Finish implementation and tests for the cheat system #3
|
@ -7,6 +7,7 @@ from json import loads
|
||||||
from forex_python.converter import CurrencyRates
|
from forex_python.converter import CurrencyRates
|
||||||
from agoradesk_py import AgoraDesk
|
from agoradesk_py import AgoraDesk
|
||||||
from httpx import ReadTimeout
|
from httpx import ReadTimeout
|
||||||
|
from pycoingecko import CoinGeckoAPI
|
||||||
|
|
||||||
# Project imports
|
# Project imports
|
||||||
from settings import settings
|
from settings import settings
|
||||||
|
@ -25,6 +26,7 @@ class Agora(object):
|
||||||
self.log = Logger("agora")
|
self.log = Logger("agora")
|
||||||
self.agora = AgoraDesk(settings.Agora.Token)
|
self.agora = AgoraDesk(settings.Agora.Token)
|
||||||
self.cr = CurrencyRates()
|
self.cr = CurrencyRates()
|
||||||
|
self.cg = CoinGeckoAPI()
|
||||||
|
|
||||||
# Cache for detecting new trades
|
# Cache for detecting new trades
|
||||||
self.last_dash = set()
|
self.last_dash = set()
|
||||||
|
@ -213,6 +215,97 @@ class Agora(object):
|
||||||
ads_total.append([ad[0], ad[1], ad[2]])
|
ads_total.append([ad[0], ad[1], ad[2]])
|
||||||
return ads_total
|
return ads_total
|
||||||
|
|
||||||
|
def enum_public_ads(self, country, currency, page=0):
|
||||||
|
ads = self.agora._api_call(api_method=f"buy-monero-online/{currency}/{country}/REVOLUT", query_values={"page": page})
|
||||||
|
if not ads["success"]:
|
||||||
|
return False
|
||||||
|
for ad in ads["response"]["data"]["ad_list"]:
|
||||||
|
if ad["data"]["online_provider"] == "REVOLUT":
|
||||||
|
yield [ad["data"]["ad_id"], ad["data"]["profile"]["username"], ad["data"]["temp_price"]]
|
||||||
|
if "pagination" in ads["response"]:
|
||||||
|
if "next" in ads["response"]["pagination"]:
|
||||||
|
page += 1
|
||||||
|
for ad in self.enum_public_ads(country, currency, page):
|
||||||
|
yield [ad[0], ad[1], ad[2]]
|
||||||
|
|
||||||
|
def wrap_public_ads(self, country, currency, rates=None):
|
||||||
|
"""
|
||||||
|
Wrapper to sort public ads.
|
||||||
|
"""
|
||||||
|
ads = list(self.enum_public_ads(country, currency))
|
||||||
|
if not rates:
|
||||||
|
base_monero_price = self.cg.get_price(ids="monero", vs_currencies=currency)["monero"][currency.lower()]
|
||||||
|
else:
|
||||||
|
base_monero_price = rates
|
||||||
|
for ad in ads:
|
||||||
|
price = float(ad[2])
|
||||||
|
rate = round(price / base_monero_price, 2)
|
||||||
|
ad.append(rate)
|
||||||
|
ads.sort(key=lambda x: float(x[2]))
|
||||||
|
return ads
|
||||||
|
|
||||||
|
def sort_ads_annotate_place(self, ads):
|
||||||
|
ads.sort(key=lambda x: float(x[2]))
|
||||||
|
for index, ad in enumerate(ads):
|
||||||
|
if ad[1] == settings.Agora.Username:
|
||||||
|
place = index
|
||||||
|
break
|
||||||
|
return (ads, place)
|
||||||
|
|
||||||
|
def update_prices(self):
|
||||||
|
our_ads = self.enum_ads()
|
||||||
|
currencies = [x[2].lower() for x in our_ads]
|
||||||
|
rates_xmr = self.cg.get_price(ids="monero", vs_currencies=currencies)
|
||||||
|
for ad_id, country, currency in our_ads:
|
||||||
|
# Get the ads for this country/currency pair
|
||||||
|
try:
|
||||||
|
public_ads = self.wrap_public_ads(country, currency, rates=rates_xmr["monero"][currency.lower()])
|
||||||
|
except KeyError:
|
||||||
|
self.log.error("Error getting public ads for currency {currency}", currency=currency)
|
||||||
|
continue
|
||||||
|
new_margin = self.autoprice(public_ads, country, currency)
|
||||||
|
new_formula = f"coingeckoxmrusd*usd{currency.lower()}*{new_margin}"
|
||||||
|
rtrn = self.agora.ad_equation(ad_id, new_formula)
|
||||||
|
if not rtrn["success"]:
|
||||||
|
self.log.error("Error updating ad {ad_id}: {response}", ad_id=ad_id, response=rtrn["response"])
|
||||||
|
self.log.info("Rate for {country}/{currency}: {margin}", country=country, currency=currency, margin=new_margin)
|
||||||
|
|
||||||
|
def autoprice(self, ads, country, currency):
|
||||||
|
"""
|
||||||
|
Helper function to automatically adjust the price up/down in certain markets
|
||||||
|
in order to gain the most profits and sales.
|
||||||
|
"""
|
||||||
|
ads, place = self.sort_ads_annotate_place(ads)
|
||||||
|
if place == 0:
|
||||||
|
if len(ads) > 1:
|
||||||
|
next_max = float(ads[1][3])
|
||||||
|
our_max_adjusted = next_max - 0.01
|
||||||
|
if our_max_adjusted > float(settings.Agora.MaxMargin):
|
||||||
|
our_max_adjusted = float(settings.Agora.MaxMargin)
|
||||||
|
# Lowball next competitor
|
||||||
|
self.log.info(
|
||||||
|
"Lowballing next competitor for {country}/{currency}: {margin}",
|
||||||
|
country=country,
|
||||||
|
currency=currency,
|
||||||
|
margin=our_max_adjusted,
|
||||||
|
)
|
||||||
|
return our_max_adjusted
|
||||||
|
else:
|
||||||
|
our_max_adjusted = float(settings.Agora.MaxMargin)
|
||||||
|
# Set the max rates as people have no choice
|
||||||
|
return our_max_adjusted
|
||||||
|
iter_count = 0
|
||||||
|
while place > 1 and not iter_count > 100:
|
||||||
|
iter_count += 1
|
||||||
|
recommended_margin = float(ads[place][2]) + 0.01
|
||||||
|
if recommended_margin <= float(settings.Agora.MinMargin):
|
||||||
|
break
|
||||||
|
ads[place][3] = recommended_margin
|
||||||
|
|
||||||
|
ads, place = self.sort_ads_annotate_place(ads)
|
||||||
|
# Return our adjusted (or left) margin
|
||||||
|
return ads[place][3]
|
||||||
|
|
||||||
def nuke_ads(self):
|
def nuke_ads(self):
|
||||||
"""
|
"""
|
||||||
Delete all of our adverts.
|
Delete all of our adverts.
|
||||||
|
|
|
@ -17,9 +17,7 @@ __copyright__ = "(C) 2021 https://codeberg.org/MarvinsCryptoTools/agoradesk_py"
|
||||||
__version__ = "0.1.0"
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
# set logging
|
# set logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
||||||
)
|
|
||||||
logging.getLogger("requests.packages.urllib3").setLevel(logging.INFO)
|
logging.getLogger("requests.packages.urllib3").setLevel(logging.INFO)
|
||||||
logging.getLogger("urllib3.connectionpool").setLevel(logging.INFO)
|
logging.getLogger("urllib3.connectionpool").setLevel(logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -60,8 +58,7 @@ class AgoraDesk:
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"User-Agent": f"agoradesk_py/{__version__} "
|
"User-Agent": f"agoradesk_py/{__version__} " f"https://codeberg.org/MarvinsCryptoTools/agoradesk_py",
|
||||||
f"https://codeberg.org/MarvinsCryptoTools/agoradesk_py",
|
|
||||||
"Authorization": self.api_key,
|
"Authorization": self.api_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +91,7 @@ class AgoraDesk:
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
response = httpx.get(
|
response = httpx.get(url=api_call_url, headers=headers, params=query_values)
|
||||||
url=api_call_url, headers=headers, params=query_values
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(response)
|
logger.debug(response)
|
||||||
result["response"] = json.loads(response.text)
|
result["response"] = json.loads(response.text)
|
||||||
|
@ -220,9 +215,7 @@ class AgoraDesk:
|
||||||
# ===========================
|
# ===========================
|
||||||
|
|
||||||
# post/feedback/{username} • Give feedback to a user
|
# post/feedback/{username} • Give feedback to a user
|
||||||
def feedback(
|
def feedback(self, username: str, feedback: str, msg: Optional[str]) -> Dict[str, Any]:
|
||||||
self, username: str, feedback: str, msg: Optional[str]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""See Agoradesk API.
|
"""See Agoradesk API.
|
||||||
|
|
||||||
https://agoradesk.com/api-docs/v1#operation/setUserFeedback
|
https://agoradesk.com/api-docs/v1#operation/setUserFeedback
|
||||||
|
@ -248,9 +241,7 @@ class AgoraDesk:
|
||||||
|
|
||||||
https://agoradesk.com/api-docs/v1#operation/markPaid
|
https://agoradesk.com/api-docs/v1#operation/markPaid
|
||||||
"""
|
"""
|
||||||
return self._api_call(
|
return self._api_call(api_method=f"contact_mark_as_paid/{trade_id}", http_method="POST")
|
||||||
api_method=f"contact_mark_as_paid/{trade_id}", http_method="POST"
|
|
||||||
)
|
|
||||||
|
|
||||||
# post/contact_cancel/{trade_id} • Cancel the trade
|
# post/contact_cancel/{trade_id} • Cancel the trade
|
||||||
def contact_cancel(
|
def contact_cancel(
|
||||||
|
@ -270,9 +261,7 @@ class AgoraDesk:
|
||||||
# post/contact_escrow/{trade_id} • Enable escrow
|
# post/contact_escrow/{trade_id} • Enable escrow
|
||||||
|
|
||||||
# get/contact_messages/{trade_id} • Get trade messages
|
# get/contact_messages/{trade_id} • Get trade messages
|
||||||
def contact_messages(
|
def contact_messages(self, trade_id: str, after: Optional[arrow.Arrow] = None) -> Dict[str, Any]:
|
||||||
self, trade_id: str, after: Optional[arrow.Arrow] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""See Agoradesk API.
|
"""See Agoradesk API.
|
||||||
|
|
||||||
https://agoradesk.com/api-docs/v1#operation/getTradeMessages
|
https://agoradesk.com/api-docs/v1#operation/getTradeMessages
|
||||||
|
@ -327,9 +316,7 @@ class AgoraDesk:
|
||||||
|
|
||||||
# Todo: Add image upload functionality
|
# Todo: Add image upload functionality
|
||||||
# post/contact_message_post/{trade_id} • Send a chat message/attachment
|
# post/contact_message_post/{trade_id} • Send a chat message/attachment
|
||||||
def contact_message_post(
|
def contact_message_post(self, trade_id: str, msg: Optional[str] = None) -> Dict[str, Any]:
|
||||||
self, trade_id: str, msg: Optional[str] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""See Agoradesk API.
|
"""See Agoradesk API.
|
||||||
|
|
||||||
https://agoradesk.com/api-docs/v1#operation/sendChatMessage
|
https://agoradesk.com/api-docs/v1#operation/sendChatMessage
|
||||||
|
@ -479,9 +466,7 @@ class AgoraDesk:
|
||||||
if track_max_amount:
|
if track_max_amount:
|
||||||
params["track_max_amount"] = 1 if track_max_amount else 0
|
params["track_max_amount"] = 1 if track_max_amount else 0
|
||||||
if require_trusted_by_advertiser:
|
if require_trusted_by_advertiser:
|
||||||
params["require_trusted_by_advertiser"] = (
|
params["require_trusted_by_advertiser"] = 1 if require_trusted_by_advertiser else 0
|
||||||
1 if require_trusted_by_advertiser else 0
|
|
||||||
)
|
|
||||||
if verified_email_required:
|
if verified_email_required:
|
||||||
params["verified_email_required"] = 1 if verified_email_required else 0
|
params["verified_email_required"] = 1 if verified_email_required else 0
|
||||||
if online_provider:
|
if online_provider:
|
||||||
|
@ -652,8 +637,7 @@ class AgoraDesk:
|
||||||
params = self._generic_search_parameters(amount, page)
|
params = self._generic_search_parameters(amount, page)
|
||||||
|
|
||||||
return self._api_call(
|
return self._api_call(
|
||||||
api_method=f"{direction}-{main_currency}-online/"
|
api_method=f"{direction}-{main_currency}-online/" f"{exchange_currency}{add_to_api_method}",
|
||||||
f"{exchange_currency}{add_to_api_method}",
|
|
||||||
query_values=params,
|
query_values=params,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -796,8 +780,7 @@ class AgoraDesk:
|
||||||
params = self._generic_search_parameters(amount, page)
|
params = self._generic_search_parameters(amount, page)
|
||||||
|
|
||||||
return self._api_call(
|
return self._api_call(
|
||||||
api_method=f"{direction}-{main_currency}-with-cash/"
|
api_method=f"{direction}-{main_currency}-with-cash/" f"{exchange_currency}/{country_code}/{lat}/{lon}",
|
||||||
f"{exchange_currency}/{country_code}/{lat}/{lon}",
|
|
||||||
query_values=params,
|
query_values=params,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -912,9 +895,7 @@ class AgoraDesk:
|
||||||
# Statistics related API Methods
|
# Statistics related API Methods
|
||||||
# ==============================
|
# ==============================
|
||||||
|
|
||||||
def moneroaverage(
|
def moneroaverage(self, currency: Optional[str] = "ticker-all-currencies") -> Dict[str, Any]:
|
||||||
self, currency: Optional[str] = "ticker-all-currencies"
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""See Agoradesk API.
|
"""See Agoradesk API.
|
||||||
|
|
||||||
https://agoradesk.com/api-docs/v1#operation/getXmrTicker and
|
https://agoradesk.com/api-docs/v1#operation/getXmrTicker and
|
||||||
|
@ -1004,9 +985,7 @@ class AgoraDesk:
|
||||||
if otp:
|
if otp:
|
||||||
params["otp"] = otp
|
params["otp"] = otp
|
||||||
|
|
||||||
return self._api_call(
|
return self._api_call(api_method="wallet-send", http_method="POST", query_values=params)
|
||||||
api_method="wallet-send", http_method="POST", query_values=params
|
|
||||||
)
|
|
||||||
|
|
||||||
def wallet_send_xmr(
|
def wallet_send_xmr(
|
||||||
self,
|
self,
|
||||||
|
@ -1031,6 +1010,4 @@ class AgoraDesk:
|
||||||
if otp:
|
if otp:
|
||||||
params["otp"] = otp
|
params["otp"] = otp
|
||||||
|
|
||||||
return self._api_call(
|
return self._api_call(api_method="wallet-send/XMR", http_method="POST", query_values=params)
|
||||||
api_method="wallet-send/XMR", http_method="POST", query_values=params
|
|
||||||
)
|
|
||||||
|
|
|
@ -293,3 +293,38 @@ class IRCCommands(object):
|
||||||
return
|
return
|
||||||
balance = rtrn["response"]["data"]["total"]["balance"]
|
balance = rtrn["response"]["data"]["total"]["balance"]
|
||||||
msg(f"Wallet balance: {balance}XMR")
|
msg(f"Wallet balance: {balance}XMR")
|
||||||
|
|
||||||
|
class pubads(object):
|
||||||
|
name = "pubads"
|
||||||
|
authed = True
|
||||||
|
helptext = "View public adverts. Usage: pubads <country> <currency>"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, revolut, tx):
|
||||||
|
if length == 3:
|
||||||
|
country = spl[1]
|
||||||
|
currency = spl[2]
|
||||||
|
rtrn = agora.wrap_public_ads(country, currency)
|
||||||
|
for ad in rtrn:
|
||||||
|
msg(f"({ad[0]}) {ad[1]} {ad[2]} {ad[3]}")
|
||||||
|
|
||||||
|
class cheat(object):
|
||||||
|
name = "cheat"
|
||||||
|
authed = True
|
||||||
|
helptext = "Cheat the markets by manipulating our prices to exploit people."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, revolut, tx):
|
||||||
|
rtrn = agora.update_prices()
|
||||||
|
msg(dumps(rtrn))
|
||||||
|
|
||||||
|
class ads(object):
|
||||||
|
name = "ads"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get all our ad regions"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, revolut, tx):
|
||||||
|
ads = agora.enum_ads()
|
||||||
|
for ad in ads:
|
||||||
|
msg(f"({ad[0]}) {ad[1]} {ad[2]}")
|
||||||
|
|
Loading…
Reference in New Issue