Compare commits
58 Commits
2025.09.01
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
4a1f1c844d | |
|
|
536866364c | |
|
|
7c33dd231d | |
|
|
96d1cf6d78 | |
|
|
b69b0d2f15 | |
|
|
18506dbaf3 | |
|
|
ca43b3dad5 | |
|
|
d06bfd9d10 | |
|
|
e354ea4d55 | |
|
|
8f3b0eb186 | |
|
|
2823dff56a | |
|
|
c42a505e49 | |
|
|
0576f93477 | |
|
|
65c406a03d | |
|
|
9e2a1dc7a1 | |
|
|
171738fa4d | |
|
|
09f9aa313c | |
|
|
451e1a63aa | |
|
|
733f6efbff | |
|
|
0d753aa3cd | |
|
|
73eff21dbb | |
|
|
37661d91eb | |
|
|
885797db01 | |
|
|
29dbdce95e | |
|
|
f5740c735c | |
|
|
bc8d621152 | |
|
|
5cf2979f38 | |
|
|
08ce7c65a0 | |
|
|
61a3545dd7 | |
|
|
05b46203ab | |
|
|
ddd1df7957 | |
|
|
ab77840bea | |
|
|
27420946cd | |
|
|
5dde1a1def | |
|
|
b36d73306d | |
|
|
f7365c0340 | |
|
|
84bada9967 | |
|
|
bb3fb692df | |
|
|
694e5a95d1 | |
|
|
56cbf51129 | |
|
|
23d85de155 | |
|
|
433813115f | |
|
|
559b95819a | |
|
|
e88ed99d6b | |
|
|
5544df9bd7 | |
|
|
7dab4d4890 | |
|
|
406067497e | |
|
|
0dd3077eb5 | |
|
|
16e1994ed1 | |
|
|
c4cfa40577 | |
|
|
3a4ce2311e | |
|
|
2e35ea9c13 | |
|
|
b594bd2007 | |
|
|
58fcff8618 | |
|
|
3daca5336e | |
|
|
069cff2402 | |
|
|
6bf3df0418 | |
|
|
ca85e454f9 |
111
changelog.txt
111
changelog.txt
|
|
@ -1,5 +1,112 @@
|
||||||
2025.09.01:
|
2025.12.01:
|
||||||
. Fixed bug in unwrapped_last_call().
|
. Modified log output of new_market_order.
|
||||||
|
. Modified Kucoin's case in min_amount_of_base.
|
||||||
|
|
||||||
|
2025.11.11:
|
||||||
|
. deals_cache and log_list cache are now 20 items long.
|
||||||
|
. Less log spam.
|
||||||
|
|
||||||
|
2025.11.08:
|
||||||
|
. broker.set_default_order_size() now saves the config file to disk after changing the value.
|
||||||
|
. Variable renaming and other small stuff.
|
||||||
|
|
||||||
|
2025.10.24:
|
||||||
|
. Toggling liquidate_after_switch now writes the config file to disk so the setting persists between trades.
|
||||||
|
. Manually switching to long now sets double_check_price to false.
|
||||||
|
. Added a few comments to switch_to_long.
|
||||||
|
|
||||||
|
2025.10.12:
|
||||||
|
. do_cleanup relocated after generating the safety orders' prices.
|
||||||
|
|
||||||
|
2025.10.11:
|
||||||
|
. Minor simplification in do_cleanup.
|
||||||
|
. Removed a couple of (no longer needed?) pauses.
|
||||||
|
|
||||||
|
2025.10.10:
|
||||||
|
. New endpoint: /refresh_log_cache.
|
||||||
|
. Fixed an error in /add_so endpoint that incremented the config setting but not the status setting.
|
||||||
|
|
||||||
|
2025.10.09:
|
||||||
|
. Cleanup is done as soon as the trader starts, rather than after sending the take profit and safety orders.
|
||||||
|
|
||||||
|
2025.10.07:
|
||||||
|
. In short traders, if there are too few safety orders (less than 67% of the max amount), safety_order_deviance is increased from 2% to 3%.
|
||||||
|
|
||||||
|
2025.10.04:
|
||||||
|
. Fixed error while logging orders in new_simulated_market_order.
|
||||||
|
. renew_tp_and_so_routine now send the take profit order first, and then the safety orders.
|
||||||
|
|
||||||
|
2025.10.03:
|
||||||
|
. New broker config option: log_orders. If set to True, the orders will be logged in orders.log under logs directory.
|
||||||
|
. New API endpoint: /toggle_log_orders.
|
||||||
|
|
||||||
|
2025.10.01:
|
||||||
|
. Fixed base fees not being taken into account.
|
||||||
|
|
||||||
|
2025.09.27:
|
||||||
|
. Added notes in every entry of deal_order_history.
|
||||||
|
. Minor refactor in renew_tp_and_so_routine.
|
||||||
|
. Added another cooldown before sending a take profit order (To give the exchange a bit more time to reflect correctly the amount of base present in the account)
|
||||||
|
. Updated cleanup routine to leave some change in the account.
|
||||||
|
|
||||||
|
2025.09.25:
|
||||||
|
. Added a pause after getting filled orders in check_status.
|
||||||
|
. Added an extra logging line in take_profit_routine.
|
||||||
|
|
||||||
|
2025.09.24:
|
||||||
|
. Added a new config option: wait_after_initial_market_order. If specifies in seconds the amount of wait time after sending the initial market order.
|
||||||
|
It should help the exchanges to report correctly the recently filled market order.
|
||||||
|
. Removed the "PAUSED" notice in the screen output that was unused.
|
||||||
|
|
||||||
|
2025.09.21:
|
||||||
|
. Fixed a bug that caused short traders to have an incorrect order size.
|
||||||
|
|
||||||
|
2025.09.20:
|
||||||
|
. Fixed bug that caused short traders to initialize using the same workflow as a long one.
|
||||||
|
|
||||||
|
2025.09.19:
|
||||||
|
. Added pageSize parameter to the open order requests when querying Kucoin.
|
||||||
|
|
||||||
|
2025.09.18:
|
||||||
|
. do_cleanup now uses get_min_quote_size.
|
||||||
|
. Added an extra price check to switch_to_long.
|
||||||
|
. Removed old check_old_long_price method.
|
||||||
|
|
||||||
|
2025.09.14:
|
||||||
|
. Refactored full order list fetching.
|
||||||
|
. Minor refactor of restart_pair_no_json.
|
||||||
|
. Pausing the trader is now done via set_pause() method.
|
||||||
|
. Reverted modification of wait time after initial market order.
|
||||||
|
. wait_time now present in broker config file.
|
||||||
|
. Minor refactorings.
|
||||||
|
|
||||||
|
2025.09.13:
|
||||||
|
. Increased wait time after initial market order.
|
||||||
|
|
||||||
|
2025.09.12:
|
||||||
|
. No retries when sending a cleanup order.
|
||||||
|
. Removed redundant try...except blocks in switch_to_long.
|
||||||
|
|
||||||
|
2025.09.11:
|
||||||
|
. Fixed bug in start_trader that called amount_to_precision with very low amounts and spammed logs.
|
||||||
|
|
||||||
|
2025.09.10:
|
||||||
|
. Deal order history now stores only the id of each order instead of the full order object.
|
||||||
|
|
||||||
|
2025.09.08:
|
||||||
|
. Re-enabled long to short autoswitch.
|
||||||
|
|
||||||
|
2025.09.07:
|
||||||
|
. Increased wait time after sending market orders.
|
||||||
|
|
||||||
|
2025.09.05:
|
||||||
|
. Now the trader supports multiple safety orders at the same time.
|
||||||
|
. Removed forcing orders when importing a trader. Maybe it will be reinstated at a later date.
|
||||||
|
. Removed endpoint /reload_safety_orders.
|
||||||
|
. New endpoints: /mod_concurrent_safety orders, /mod_boosted_concurrent_safety_orders and /force_trader_close.
|
||||||
|
. Modified cleanup routine.
|
||||||
|
. Default wait_time back to 0.5 seconds.
|
||||||
|
. General optimizations.
|
||||||
|
|
||||||
2025.08.19:
|
2025.08.19:
|
||||||
. Improved log trimming.
|
. Improved log trimming.
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ class ConfigHandler:
|
||||||
"order_size": self.broker.get_default_order_size(),
|
"order_size": self.broker.get_default_order_size(),
|
||||||
"no_of_safety_orders": 30,
|
"no_of_safety_orders": 30,
|
||||||
"max_short_safety_orders": 45,
|
"max_short_safety_orders": 45,
|
||||||
|
"concurrent_safety_orders": 3,
|
||||||
|
"boosted_concurrent_safety_orders": 5,
|
||||||
"safety_order_deviance": 2,
|
"safety_order_deviance": 2,
|
||||||
"safety_order_scale": 0.0105,
|
"safety_order_scale": 0.0105,
|
||||||
"dynamic_so_deviance": True,
|
"dynamic_so_deviance": True,
|
||||||
|
|
@ -35,6 +37,9 @@ class ConfigHandler:
|
||||||
"force_restart_if_retries_exhausted": False,
|
"force_restart_if_retries_exhausted": False,
|
||||||
"check_old_long_price": False #switch_to_short should flip this to True unless stated
|
"check_old_long_price": False #switch_to_short should flip this to True unless stated
|
||||||
}
|
}
|
||||||
|
# if self.broker.get_exchange_name()=="kucoin":
|
||||||
|
# self.default_config_dictionary["concurrent_safety_orders"]=1
|
||||||
|
# self.default_config_dictionary["boosted_concurrent_safety_orders"]=1
|
||||||
self.config_file_path = f"configs/{pair.split('/')[0]}{pair.split('/')[1]}.json"
|
self.config_file_path = f"configs/{pair.split('/')[0]}{pair.split('/')[1]}.json"
|
||||||
self.config_dictionary = self.default_config_dictionary.copy()
|
self.config_dictionary = self.default_config_dictionary.copy()
|
||||||
|
|
||||||
|
|
@ -68,6 +73,12 @@ class ConfigHandler:
|
||||||
def get_max_short_safety_orders(self):
|
def get_max_short_safety_orders(self):
|
||||||
return self.config_dictionary["max_short_safety_orders"]
|
return self.config_dictionary["max_short_safety_orders"]
|
||||||
|
|
||||||
|
def get_concurrent_safety_orders(self):
|
||||||
|
return self.config_dictionary["concurrent_safety_orders"]
|
||||||
|
|
||||||
|
def get_boosted_concurrent_safety_orders(self):
|
||||||
|
return self.config_dictionary["boosted_concurrent_safety_orders"]
|
||||||
|
|
||||||
def get_safety_order_deviance(self):
|
def get_safety_order_deviance(self):
|
||||||
return self.config_dictionary["safety_order_deviance"]
|
return self.config_dictionary["safety_order_deviance"]
|
||||||
|
|
||||||
|
|
@ -173,6 +184,20 @@ class ConfigHandler:
|
||||||
self.config_dictionary["max_short_safety_orders"] = max_short_safety_orders
|
self.config_dictionary["max_short_safety_orders"] = max_short_safety_orders
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def set_concurrent_safety_orders(self, concurrent_safety_orders: int):
|
||||||
|
# if not isinstance(concurrent_safety_orders, int):
|
||||||
|
# self.broker.logger.log_this(f"Max concurrent safety orders provided is not an integer",1,self.get_pair())
|
||||||
|
# return 1
|
||||||
|
self.config_dictionary["concurrent_safety_orders"] = concurrent_safety_orders
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def set_boosted_concurrent_safety_orders(self, boosted_concurrent_safety_orders: int):
|
||||||
|
# if not isinstance(concurrent_safety_orders, int):
|
||||||
|
# self.broker.logger.log_this(f"Max concurrent safety orders provided is not an integer",1,self.get_pair())
|
||||||
|
# return 1
|
||||||
|
self.config_dictionary["boosted_concurrent_safety_orders"] = boosted_concurrent_safety_orders
|
||||||
|
return 0
|
||||||
|
|
||||||
def set_safety_order_deviance(self, safety_order_deviance: int):
|
def set_safety_order_deviance(self, safety_order_deviance: int):
|
||||||
# if not isinstance(safety_order_deviance, int):
|
# if not isinstance(safety_order_deviance, int):
|
||||||
# self.broker.logger.log_this(f"Safety order deviance provided is not an integer",1,self.get_pair())
|
# self.broker.logger.log_this(f"Safety order deviance provided is not an integer",1,self.get_pair())
|
||||||
|
|
@ -227,6 +252,7 @@ class ConfigHandler:
|
||||||
# self.broker.logger.log_this(f"liquidate_after_switch must be a boolean",1,self.get_pair())
|
# self.broker.logger.log_this(f"liquidate_after_switch must be a boolean",1,self.get_pair())
|
||||||
# return 1
|
# return 1
|
||||||
self.config_dictionary["liquidate_after_switch"] = liquidate_after_switch
|
self.config_dictionary["liquidate_after_switch"] = liquidate_after_switch
|
||||||
|
self.save_to_file()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def set_tp_mode(self, tp_mode: int):
|
def set_tp_mode(self, tp_mode: int):
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,20 @@ class Broker:
|
||||||
self.broker_config = broker_config
|
self.broker_config = broker_config
|
||||||
self.exchange = exchange
|
self.exchange = exchange
|
||||||
self.last_price = 0
|
self.last_price = 0
|
||||||
self.wait_time = 1 #Default wait time for API breathing room
|
|
||||||
self.cooldown_multiplier = 2 #Default cooldown multiplier value
|
|
||||||
if "cooldown_multiplier" in self.broker_config:
|
|
||||||
self.cooldown_multiplier = self.broker_config["cooldown_multiplier"]
|
|
||||||
self.wait_before_new_safety_order = 1
|
|
||||||
if "wait_before_new_safety_order" in self.broker_config:
|
|
||||||
self.wait_before_new_safety_order = self.broker_config["wait_before_new_safety_order"]
|
|
||||||
self.empty_order = {"id": "", "status": "", "filled": 0, "remaining": 0, "price": 0, "cost": 0, "fees": [], "symbol": ""}
|
self.empty_order = {"id": "", "status": "", "filled": 0, "remaining": 0, "price": 0, "cost": 0, "fees": [], "symbol": ""}
|
||||||
self.retries = self.broker_config["retries"] if "retries" in self.broker_config else 5
|
|
||||||
self.slippage_default_threshold = self.broker_config["slippage_default_threshold"] if "slippage_default_threshold" in self.broker_config else .03
|
|
||||||
self.logger = Logger(self.broker_config)
|
|
||||||
self.follow_order_history = False #This should be a toggle in config_file
|
|
||||||
self.write_order_history = False #This should be a toggle in config_file
|
|
||||||
|
|
||||||
|
#Default values
|
||||||
|
self.wait_time = self.broker_config.get("wait_time",.5)
|
||||||
|
self.cooldown_multiplier = self.broker_config.get("cooldown_multiplier",2)
|
||||||
|
self.wait_after_initial_market_order = self.broker_config.get("wait_after_initial_market_order",1)
|
||||||
|
self.wait_before_new_safety_order = self.broker_config.get("wait_before_new_safety_order",1)
|
||||||
|
self.retries = self.broker_config.get("retries",5)
|
||||||
|
self.slippage_default_threshold = self.broker_config.get("slippage_default_threshold",.03)
|
||||||
|
self.follow_order_history = self.broker_config.get("follow_order_history",False)
|
||||||
|
self.write_order_history = self.broker_config.get("write_order_history", False)
|
||||||
|
self.logger = Logger(self.broker_config)
|
||||||
|
self.log_orders = self.broker_config.get("log_orders",False)
|
||||||
|
|
||||||
#Initialize database
|
#Initialize database
|
||||||
self.profits_database_filename = "profits/profits_database.db"
|
self.profits_database_filename = "profits/profits_database.db"
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ class Broker:
|
||||||
self.markets = self.exchange.load_markets()
|
self.markets = self.exchange.load_markets()
|
||||||
|
|
||||||
#Populates deals cache
|
#Populates deals cache
|
||||||
self.deals_cache_length = 10
|
self.deals_cache_length = 20
|
||||||
self.deals_list = self.preload_deals(amount_to_preload=self.deals_cache_length)
|
self.deals_list = self.preload_deals(amount_to_preload=self.deals_cache_length)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,6 +82,16 @@ class Broker:
|
||||||
def get_deals_cache(self):
|
def get_deals_cache(self):
|
||||||
return self.deals_list
|
return self.deals_list
|
||||||
|
|
||||||
|
|
||||||
|
def get_log_orders(self):
|
||||||
|
return self.log_orders
|
||||||
|
|
||||||
|
|
||||||
|
def set_log_orders(self,log_orders:bool):
|
||||||
|
self.log_orders = log_orders
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def get_symbol(self,pair):
|
def get_symbol(self,pair):
|
||||||
if "/" in pair:
|
if "/" in pair:
|
||||||
return pair
|
return pair
|
||||||
|
|
@ -174,7 +184,7 @@ class Broker:
|
||||||
|
|
||||||
query = f"SELECT * FROM profits_table WHERE pair = ? ORDER BY timestamp DESC LIMIT 1;"
|
query = f"SELECT * FROM profits_table WHERE pair = ? ORDER BY timestamp DESC LIMIT 1;"
|
||||||
with self._cur() as cur:
|
with self._cur() as cur:
|
||||||
cur.execute(query, (order['symbol'],))
|
cur.execute(query, (order["symbol"],))
|
||||||
result = cur.fetchone()
|
result = cur.fetchone()
|
||||||
if result is None:
|
if result is None:
|
||||||
return False
|
return False
|
||||||
|
|
@ -194,6 +204,13 @@ class Broker:
|
||||||
self.cooldown_multiplier = value
|
self.cooldown_multiplier = value
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def get_wait_after_initial_market_order(self):
|
||||||
|
return self.wait_after_initial_market_order
|
||||||
|
|
||||||
|
def set_wait_after_initial_market_order(self, value:float):
|
||||||
|
self.wait_after_initial_market_order = value
|
||||||
|
return 0
|
||||||
|
|
||||||
def get_wait_before_new_safety_order(self):
|
def get_wait_before_new_safety_order(self):
|
||||||
return self.wait_before_new_safety_order
|
return self.wait_before_new_safety_order
|
||||||
|
|
||||||
|
|
@ -207,6 +224,7 @@ class Broker:
|
||||||
def set_default_order_size(self,size):
|
def set_default_order_size(self,size):
|
||||||
try:
|
try:
|
||||||
self.broker_config["default_order_size"] = float(size)
|
self.broker_config["default_order_size"] = float(size)
|
||||||
|
self.rewrite_config_file()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in set_default_order_size: {e}",1)
|
self.logger.log_this(f"Exception in set_default_order_size: {e}",1)
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -379,6 +397,11 @@ class Broker:
|
||||||
if self.get_exchange_name()=="binance":
|
if self.get_exchange_name()=="binance":
|
||||||
a = self.exchange.fetch_last_prices(pair_list)
|
a = self.exchange.fetch_last_prices(pair_list)
|
||||||
return {x: a[x]["price"] for x in a.keys()}
|
return {x: a[x]["price"] for x in a.keys()}
|
||||||
|
elif self.get_exchange_name()=="kucoin":
|
||||||
|
a = self.exchange.fetch_tickers(pair_list)
|
||||||
|
if pair_list is None:
|
||||||
|
return {x: a[x]["close"] for x in a.keys()}
|
||||||
|
return {x: a[x]["close"] for x in a.keys() if x in pair_list}
|
||||||
else:
|
else:
|
||||||
a = self.exchange.fetch_tickers()
|
a = self.exchange.fetch_tickers()
|
||||||
if pair_list is None:
|
if pair_list is None:
|
||||||
|
|
@ -455,7 +478,7 @@ class Broker:
|
||||||
try:
|
try:
|
||||||
return orderbook["bids"][0][0]
|
return orderbook["bids"][0][0]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception getting top mid price: {e}",1,symbol)
|
self.logger.log_this(f"Exception getting top bid price: {e}",1,symbol)
|
||||||
return self.get_ticker_price(symbol)
|
return self.get_ticker_price(symbol)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -500,28 +523,6 @@ class Broker:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def fetch_full_orders(self,pairs=None) -> list:
|
|
||||||
'''
|
|
||||||
Returns a list of all orders on the exchange
|
|
||||||
|
|
||||||
:param pairs: list of pairs to get orders for
|
|
||||||
:return: list of orders
|
|
||||||
'''
|
|
||||||
|
|
||||||
if pairs is None:
|
|
||||||
pairs = []
|
|
||||||
try:
|
|
||||||
orders = []
|
|
||||||
if self.get_exchange_name()=="binance":
|
|
||||||
orders = self.get_opened_orders_binance(pairs)
|
|
||||||
else:
|
|
||||||
orders = self.get_opened_orders()
|
|
||||||
return [] if orders is None else orders
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.log_this(f"Exception in fetch_full_orders: {e}",2)
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_open_orders(self,pairs=None) -> list:
|
def fetch_open_orders(self,pairs=None) -> list:
|
||||||
'''
|
'''
|
||||||
Returns a list of all open orders on the exchange
|
Returns a list of all open orders on the exchange
|
||||||
|
|
@ -534,8 +535,17 @@ class Broker:
|
||||||
pairs = []
|
pairs = []
|
||||||
try:
|
try:
|
||||||
if self.get_exchange_name()=="binance":
|
if self.get_exchange_name()=="binance":
|
||||||
return self.get_opened_orders_binance(pairs)
|
if self.broker_config.get("unified_order_query"):
|
||||||
return self.get_opened_orders()
|
return self.exchange.fetch_open_orders()
|
||||||
|
result = []
|
||||||
|
for pair in pairs:
|
||||||
|
a = self.exchange.fetch_open_orders(pair)
|
||||||
|
result.extend(iter(a))
|
||||||
|
return result
|
||||||
|
elif self.get_exchange_name()=="kucoin":
|
||||||
|
return self.exchange.fetch_open_orders(params={"pageSize": "500"})
|
||||||
|
else:
|
||||||
|
return self.exchange.fetch_open_orders()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in fetch_open_orders: {e}",2)
|
self.logger.log_this(f"Exception in fetch_open_orders: {e}",2)
|
||||||
return []
|
return []
|
||||||
|
|
@ -556,11 +566,11 @@ class Broker:
|
||||||
return self.get_closed_orders_binance(pairs)
|
return self.get_closed_orders_binance(pairs)
|
||||||
return self.get_closed_orders()
|
return self.get_closed_orders()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in fetch_open_orders: {e}",2)
|
self.logger.log_this(f"Exception in fetch_closed_orders: {e}",2)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def get_opened_orders(self,no_retries=False): #It should return a list of all opened orders
|
def get_closed_orders(self,pair=None,no_retries=False): #It should return a list of all opened orders
|
||||||
'''
|
'''
|
||||||
Returns a list of all the open orders on the exchange
|
Returns a list of all the open orders on the exchange
|
||||||
|
|
||||||
|
|
@ -571,28 +581,7 @@ class Broker:
|
||||||
retries = self.retries
|
retries = self.retries
|
||||||
while retries>0:
|
while retries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.fetch_open_orders()
|
return self.exchange.fetch_closed_orders(pair)
|
||||||
except Exception as e:
|
|
||||||
self.logger.log_this(f"Exception in get_opened_orders: {e}",1)
|
|
||||||
if no_retries:
|
|
||||||
break
|
|
||||||
time.sleep(self.wait_time)
|
|
||||||
retries-=1
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def get_closed_orders(self,no_retries=False): #It should return a list of all opened orders
|
|
||||||
'''
|
|
||||||
Returns a list of all the open orders on the exchange
|
|
||||||
|
|
||||||
:param pairs: list of pairs
|
|
||||||
:return: list of all the open orders on the exchange
|
|
||||||
'''
|
|
||||||
|
|
||||||
retries = self.retries
|
|
||||||
while retries>0:
|
|
||||||
try:
|
|
||||||
return self.exchange.fetch_closed_orders()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in get_closed_orders: {e}",1)
|
self.logger.log_this(f"Exception in get_closed_orders: {e}",1)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -602,27 +591,6 @@ class Broker:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def get_opened_orders_binance(self,pairs):
|
|
||||||
'''
|
|
||||||
Returns a list of all the open orders on the exchange
|
|
||||||
|
|
||||||
:param pairs: list of pairs
|
|
||||||
:return: list of all the open orders on the exchange
|
|
||||||
'''
|
|
||||||
|
|
||||||
try:
|
|
||||||
if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True:
|
|
||||||
return self.exchange.fetch_open_orders()
|
|
||||||
result = []
|
|
||||||
for pair in pairs:
|
|
||||||
a = self.exchange.fetch_open_orders(pair)
|
|
||||||
result.extend(iter(a))
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.log_this(f"Exception in get_opened_orders_binance: {e}",1)
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def get_closed_orders_binance(self,pairs):
|
def get_closed_orders_binance(self,pairs):
|
||||||
'''
|
'''
|
||||||
Returns a list of all the closed orders on the exchange
|
Returns a list of all the closed orders on the exchange
|
||||||
|
|
@ -632,7 +600,7 @@ class Broker:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True:
|
if self.broker_config.get("unified_order_query"):
|
||||||
return self.exchange.fetch_closed_orders()
|
return self.exchange.fetch_closed_orders()
|
||||||
result = []
|
result = []
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
|
|
@ -654,16 +622,15 @@ class Broker:
|
||||||
:return: 0 if order was succesfully canceled, 1 if not
|
:return: 0 if order was succesfully canceled, 1 if not
|
||||||
'''
|
'''
|
||||||
|
|
||||||
pair = symbol
|
|
||||||
tries = self.retries//2
|
tries = self.retries//2
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
while self.get_order(id,pair)["status"]=="open":
|
while self.get_order(id,symbol)["status"]=="open":
|
||||||
self.exchange.cancel_order(id,symbol=pair)
|
self.exchange.cancel_order(id,symbol)
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
return 0
|
return 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.get_order(id,pair)["status"]=="canceled":
|
if self.get_order(id,symbol)["status"]=="canceled":
|
||||||
return 0
|
return 0
|
||||||
self.logger.log_this(f"Exception in cancel_order: id {id} - exception: {e}",1)
|
self.logger.log_this(f"Exception in cancel_order: id {id} - exception: {e}",1)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -697,7 +664,7 @@ class Broker:
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
|
|
||||||
def new_simulated_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False):
|
def new_simulated_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False,log=""):
|
||||||
'''
|
'''
|
||||||
TODO: Emulating Market Orders With Limit Orders
|
TODO: Emulating Market Orders With Limit Orders
|
||||||
|
|
||||||
|
|
@ -724,26 +691,29 @@ class Broker:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
retries = self.retries//2
|
retries = self.retries//2
|
||||||
pair = symbol
|
|
||||||
while retries>0:
|
while retries>0:
|
||||||
try:
|
try:
|
||||||
if self.get_exchange_name()=="gateio" and side=="buy" and not amount_in_base:
|
if self.get_exchange_name()=="gateio" and side=="buy" and not amount_in_base:
|
||||||
new_order = self.exchange.create_market_buy_order_with_cost(pair, size)
|
new_order = self.exchange.create_market_buy_order_with_cost(symbol, size)
|
||||||
|
if self.log_orders:
|
||||||
|
self.logger.log_order(f"New simulated market order: Symbol: {symbol} - Side: {side} - Size: {size} - ID: {new_order['id']} - Origin: {log}")
|
||||||
else:
|
else:
|
||||||
order_book = self.get_order_book(symbol)
|
order_book = self.get_order_book(symbol)
|
||||||
if order_book=={}:
|
if order_book=={}:
|
||||||
self.logger.log_this(f"new_simulated_market_order. Order book returned an empty dictionary",1,symbol)
|
self.logger.log_this(f"new_simulated_market_order. Order book returned an empty dictionary",1,symbol)
|
||||||
return self.empty_order
|
return self.empty_order
|
||||||
if amount_in_base or side!="buy":
|
if amount_in_base or side!="buy":
|
||||||
base_amount = self.amount_to_precision(pair,size)
|
base_amount = self.amount_to_precision(symbol,size)
|
||||||
else:
|
else:
|
||||||
avg_price = self.average_price_depth(order_book,size,"sell")
|
avg_price = self.average_price_depth(order_book,size,"sell")
|
||||||
base_amount = size/avg_price if avg_price is not None else size/self.get_ticker_price(symbol)
|
base_amount = size/avg_price if avg_price is not None else size/self.get_ticker_price(symbol)
|
||||||
price = self.find_minimum_viable_price(order_book,base_amount,side)
|
price = self.find_minimum_viable_price(order_book,base_amount,side)
|
||||||
#Maybe check for slippage here instead of within the trader itself? idk
|
#Maybe check for slippage here instead of within the trader itself? idk
|
||||||
new_order = self.exchange.create_order(pair,"limit",side,base_amount,price)
|
new_order = self.exchange.create_order(symbol,"limit",side,base_amount,price)
|
||||||
|
if self.log_orders:
|
||||||
|
self.logger.log_order(f"New simulated market order: Symbol: {symbol} - Side: {side} - Size: {size} - Price: {price} - ID: {new_order['id']} - Origin: {log}")
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
return self.get_order(new_order["id"],pair)
|
return self.get_order(new_order["id"],symbol)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"new_simulated_market_order exception: {e}",1,symbol)
|
self.logger.log_this(f"new_simulated_market_order exception: {e}",1,symbol)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -788,7 +758,7 @@ class Broker:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def new_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False): #It should send a new market order to the exchange
|
def new_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False, log=""): #It should send a new market order to the exchange
|
||||||
'''
|
'''
|
||||||
Sends a new market order to the exchange.
|
Sends a new market order to the exchange.
|
||||||
|
|
||||||
|
|
@ -802,24 +772,24 @@ class Broker:
|
||||||
if self.broker_config["simulate_market_orders"]:
|
if self.broker_config["simulate_market_orders"]:
|
||||||
return self.new_simulated_market_order(symbol,size,side,amount_in_base=amount_in_base)
|
return self.new_simulated_market_order(symbol,size,side,amount_in_base=amount_in_base)
|
||||||
retries = self.retries
|
retries = self.retries
|
||||||
pair = symbol
|
|
||||||
while retries>0:
|
while retries>0:
|
||||||
try:
|
try:
|
||||||
if side=="buy":
|
if side=="buy":
|
||||||
to_buy = float(size)
|
to_buy = float(size)
|
||||||
if not amount_in_base:
|
if not amount_in_base:
|
||||||
to_buy = float(size)/self.get_top_ask_price(pair)
|
to_buy = float(size)/self.get_top_ask_price(symbol)
|
||||||
amount = self.amount_to_precision(pair,to_buy)
|
amount = self.amount_to_precision(symbol,to_buy)
|
||||||
else:
|
else:
|
||||||
amount = self.amount_to_precision(pair,size) #Market sell orders are always nominated in base currency
|
amount = self.amount_to_precision(symbol,size) #Market sell orders are always nominated in base currency
|
||||||
|
|
||||||
order_to_send = self.exchange.create_order(pair,"market",side,amount)
|
order_to_send = self.exchange.create_order(symbol,"market",side,amount)
|
||||||
|
if self.log_orders:
|
||||||
|
self.logger.log_order(f"New market order: Symbol: {symbol} - Side: {side} - Size: {size} - ID: {order_to_send['id']} - Origin: {log}")
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
# Wait a bit more when dealing with Kucoin
|
|
||||||
|
|
||||||
return self.get_order(order_to_send["id"],pair)
|
return self.get_order(order_to_send["id"],symbol)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in new_market_order: {e}",1,pair)
|
self.logger.log_this(f"Exception in new_market_order: {e} - Side: {side} - Size: {size}",1,symbol)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
break
|
break
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
|
|
@ -867,7 +837,41 @@ class Broker:
|
||||||
return "the lowest price limit for sell orders is" in str(error_object).lower()
|
return "the lowest price limit for sell orders is" in str(error_object).lower()
|
||||||
|
|
||||||
|
|
||||||
def new_limit_order(self,symbol,size,side,price,no_retries=False):
|
# def new_limit_orders(self, orders: list) -> list:
|
||||||
|
# sent_orders = []
|
||||||
|
# #Send the orders
|
||||||
|
# tries = self.retries
|
||||||
|
# while tries>=0:
|
||||||
|
# try:
|
||||||
|
# sent_orders = self.exchange.create_orders(orders)
|
||||||
|
# except Exception as e:
|
||||||
|
# self.logger.log_this(f"Exception while sending safety orders: {e}",1)
|
||||||
|
# tries-=1
|
||||||
|
# time.sleep(self.wait_time)
|
||||||
|
# if tries==0:
|
||||||
|
# return []
|
||||||
|
|
||||||
|
# #Retrieve the orders from the exchange by id to confirm that they were sent
|
||||||
|
# #Specially for OKX, since the orders that create_orders return are empty (only id is present)
|
||||||
|
# returned_orders = []
|
||||||
|
# for order in sent_orders:
|
||||||
|
# tries = self.retries
|
||||||
|
# while tries>=0:
|
||||||
|
# try:
|
||||||
|
# returned_orders.append(self.get_order(order["id"],order["symbol"]))
|
||||||
|
# time.sleep(self.wait_time)
|
||||||
|
# except Exception as e:
|
||||||
|
# self.logger.log_this(f"Exception while retrieving safety orders: {e}",1)
|
||||||
|
# tries-=1
|
||||||
|
# if tries==0:
|
||||||
|
# if self.get_exchange_name()=="okex":
|
||||||
|
# return returned_orders
|
||||||
|
# returned_orders.append(order) #In the case of the other exchanges, we just assume that the order was sent and append it.
|
||||||
|
# time.sleep(self.wait_time)
|
||||||
|
# return returned_orders
|
||||||
|
|
||||||
|
|
||||||
|
def new_limit_order(self,symbol,size,side,price,no_retries=False,log=""):
|
||||||
'''
|
'''
|
||||||
Sends a new limit order.
|
Sends a new limit order.
|
||||||
|
|
||||||
|
|
@ -879,20 +883,15 @@ class Broker:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
pair = symbol
|
|
||||||
while tries>=0:
|
while tries>=0:
|
||||||
try:
|
try:
|
||||||
order_to_send = self.exchange.create_order(pair,"limit",side,self.amount_to_precision(pair,size),price)
|
order_to_send = self.exchange.create_order(symbol,"limit",side,self.amount_to_precision(symbol,size),price)
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
return self.get_order(order_to_send["id"],pair)
|
if self.log_orders:
|
||||||
#if order_to_send["amount"] is not None: # Because Kucoin etc etc
|
self.logger.log_order(f"New limit order: Symbol: {symbol} - Side: {side} - Size: {size} - Price: {price} - ID: {order_to_send['id']} - Notes: {log}")
|
||||||
# return self.get_order(order_to_send["id"],pair) #
|
return self.get_order(order_to_send["id"],symbol)
|
||||||
#self.logger.log_this(f"Error sending order: Null order returned",2,pair) #
|
|
||||||
#self.cancel_order(order_to_send["id"],symbol,no_retries=True) #
|
|
||||||
#retries-=1
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(pair,size)} - Exception: {e}",1,symbol)
|
self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(symbol,size)} - Exception: {e}",1,symbol)
|
||||||
if self.not_enough_balance_error(e):
|
if self.not_enough_balance_error(e):
|
||||||
if tries<=self.retries//2: #Halves the amount of retries if there is a balance error.
|
if tries<=self.retries//2: #Halves the amount of retries if there is a balance error.
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -923,10 +922,9 @@ class Broker:
|
||||||
if id=="":
|
if id=="":
|
||||||
return self.empty_order
|
return self.empty_order
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
pair = symbol
|
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.fetch_order(id,symbol=pair)
|
return self.exchange.fetch_order(id,symbol)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in get_order: {e}",1,symbol)
|
self.logger.log_this(f"Exception in get_order: {e}",1,symbol)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -944,10 +942,9 @@ class Broker:
|
||||||
:return: The market information.
|
:return: The market information.
|
||||||
'''
|
'''
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
pair = symbol
|
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.market(pair)
|
return self.exchange.market(symbol)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in fetch_market: {e}",1,symbol)
|
self.logger.log_this(f"Exception in fetch_market: {e}",1,symbol)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -965,10 +962,9 @@ class Broker:
|
||||||
:return: The ticker information.
|
:return: The ticker information.
|
||||||
'''
|
'''
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
pair = symbol
|
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.fetch_ticker(pair)
|
return self.exchange.fetch_ticker(symbol)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in get_ticker: {e}")
|
self.logger.log_this(f"Exception in get_ticker: {e}")
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -992,9 +988,9 @@ class Broker:
|
||||||
if self.get_exchange_name() in ["okex","bybit"]:
|
if self.get_exchange_name() in ["okex","bybit"]:
|
||||||
return float(market["limits"]["amount"]["min"])
|
return float(market["limits"]["amount"]["min"])
|
||||||
elif self.get_exchange_name() in ["kucoin"]:
|
elif self.get_exchange_name() in ["kucoin"]:
|
||||||
return (float(market["limits"]["cost"]["min"])+.1)/self.get_ticker_price(pair)
|
return max(float(market["limits"]["amount"]["min"]),(float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair))
|
||||||
elif self.get_exchange_name() in ["gateio"]:
|
elif self.get_exchange_name() in ["gateio"]:
|
||||||
return (float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair)
|
return (float(market["limits"]["cost"]["min"])+.1)/self.get_ticker_price(pair)
|
||||||
elif self.get_exchange_name()=="binance":
|
elif self.get_exchange_name()=="binance":
|
||||||
for line in market["info"]["filters"]:
|
for line in market["info"]["filters"]:
|
||||||
if line["filterType"] == "NOTIONAL":
|
if line["filterType"] == "NOTIONAL":
|
||||||
|
|
@ -1032,8 +1028,8 @@ class Broker:
|
||||||
|
|
||||||
:param pair: pair
|
:param pair: pair
|
||||||
:return: step size
|
:return: step size
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
market = self.fetch_market(pair)
|
market = self.fetch_market(pair)
|
||||||
if market is None:
|
if market is None:
|
||||||
return None
|
return None
|
||||||
|
|
@ -1056,7 +1052,7 @@ class Logger:
|
||||||
self.broker_config = broker_config
|
self.broker_config = broker_config
|
||||||
self.exchange_name = self.broker_config["exchange"]
|
self.exchange_name = self.broker_config["exchange"]
|
||||||
self.tg_credentials = credentials.get_credentials("telegram")
|
self.tg_credentials = credentials.get_credentials("telegram")
|
||||||
self.log_list_max_length = 10
|
self.log_list_max_length = 20 # log cache
|
||||||
self.log_list = collections.deque(maxlen=self.log_list_max_length)
|
self.log_list = collections.deque(maxlen=self.log_list_max_length)
|
||||||
self.preload_logs()
|
self.preload_logs()
|
||||||
|
|
||||||
|
|
@ -1072,6 +1068,16 @@ class Logger:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_logs(self):
|
||||||
|
try:
|
||||||
|
self.log_list.clear()
|
||||||
|
self.preload_logs()
|
||||||
|
return 0
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def set_log_list_max_length(self, amount):
|
def set_log_list_max_length(self, amount):
|
||||||
self.log_list_max_length = amount
|
self.log_list_max_length = amount
|
||||||
return self.log_list_max_length
|
return self.log_list_max_length
|
||||||
|
|
@ -1108,6 +1114,9 @@ class Logger:
|
||||||
self.log_this(f"Error in send_tg_message: {e}",1)
|
self.log_this(f"Error in send_tg_message: {e}",1)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
def log_order(self,message):
|
||||||
|
with open(f"logs/orders.log","a") as log_file:
|
||||||
|
log_file.write(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {message}\n"))
|
||||||
|
|
||||||
def log_this(self,message,level=2,pair=None):
|
def log_this(self,message,level=2,pair=None):
|
||||||
'''
|
'''
|
||||||
|
|
@ -1129,26 +1138,16 @@ class Logger:
|
||||||
#Write to log file
|
#Write to log file
|
||||||
with open(f"logs/{self.exchange_name}.log","a") as log_file:
|
with open(f"logs/{self.exchange_name}.log","a") as log_file:
|
||||||
log_file.write(text+"\n")
|
log_file.write(text+"\n")
|
||||||
log_file.close()
|
|
||||||
|
|
||||||
#Append to log list
|
#Append to log list
|
||||||
self.log_list.append(text)
|
self.log_list.append(text)
|
||||||
|
|
||||||
#Trim log list
|
|
||||||
#self.log_list = self.log_list[-self.log_list_max_length:]
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Can't write log file")
|
print("Can't write log file")
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
if level<1:
|
if level<1:
|
||||||
self.send_tg_message(f"{self.broker_config['exchange'].capitalize()} | {pair_data}{message}",ignore_config=level==-1)
|
self.send_tg_message(f"{self.broker_config['exchange'].capitalize()} | {pair_data}{message}",ignore_config=level==-1)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class Order:
|
|
||||||
def __init__(self, order: dict = {}):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
490
main.py
490
main.py
|
|
@ -18,7 +18,7 @@ import exchange_wrapper
|
||||||
import trader
|
import trader
|
||||||
|
|
||||||
|
|
||||||
version = "2025.09.01"
|
version = "2025.12.01"
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||||
|
|
@ -39,8 +39,9 @@ worker_threads_overprovisioning = 3 #Number of worker threads to create over
|
||||||
#Only use 0 if you are sure that you won't be adding any.
|
#Only use 0 if you are sure that you won't be adding any.
|
||||||
executor = None
|
executor = None
|
||||||
|
|
||||||
|
#Shutdown handler
|
||||||
def shutdown_handler(signum, _):
|
def shutdown_handler(signum, _):
|
||||||
broker.logger.log_this(f"Received signal {signum}, shutting down as gracefully as possible...", 2)
|
broker.logger.log_this(f"Received signal {signum}, shutting down.", 2)
|
||||||
if executor:
|
if executor:
|
||||||
executor.shutdown(wait=True, timeout=5)
|
executor.shutdown(wait=True, timeout=5)
|
||||||
os_exit(0)
|
os_exit(0)
|
||||||
|
|
@ -86,7 +87,7 @@ def time_to_unix(year: str, month: str, day: str) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def import_instance(base: str, quote: str, forced_tp_id = None, forced_so_id = None) -> int:
|
def import_instance(base: str, quote: str) -> int:
|
||||||
'''
|
'''
|
||||||
Imports an previously running trader instance from the status file.
|
Imports an previously running trader instance from the status file.
|
||||||
|
|
||||||
|
|
@ -98,7 +99,7 @@ def import_instance(base: str, quote: str, forced_tp_id = None, forced_so_id = N
|
||||||
int: 0 if successful
|
int: 0 if successful
|
||||||
'''
|
'''
|
||||||
broker.logger.log_this(f"Importing {base}/{quote}")
|
broker.logger.log_this(f"Importing {base}/{quote}")
|
||||||
instances_to_add.append(trader.trader(broker,f"{base}/{quote}",is_import=True,forced_tp_id=forced_tp_id,forced_so_id=forced_so_id))
|
instances_to_add.append(trader.trader(broker,f"{base}/{quote}",is_import=True))
|
||||||
if f"{base}{quote}" not in tickers:
|
if f"{base}{quote}" not in tickers:
|
||||||
tickers.append(f"{base}{quote}")
|
tickers.append(f"{base}{quote}")
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -245,24 +246,30 @@ def restart_pair_no_json(base: str, quote: str) -> int:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
order_list = broker.fetch_full_orders(tickers)
|
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.pause = True
|
instance.set_pause(True, "Restarting trader")
|
||||||
#Backing up old status file
|
#Backing up old status file
|
||||||
instance.status.save_to_file(is_backup=True)
|
instance.status.save_to_file(is_backup=True)
|
||||||
#Here, we could open a duster (if needed)
|
|
||||||
for order in order_list:
|
broker.logger.log_this(f"Cancelling old take profit order",2,symbol)
|
||||||
if order["symbol"]==symbol and instance.config.get_is_short() and order["side"]=="sell":
|
try:
|
||||||
broker.logger.log_this(f"Cancelling old sell orders",2,symbol)
|
old_tp_order = instance.status.get_take_profit_order()
|
||||||
broker.cancel_order(order["id"],order["symbol"])
|
broker.cancel_order(old_tp_order["id"],old_tp_order["symbol"])
|
||||||
elif order["symbol"]==symbol and not instance.config.get_is_short() and order["side"]=="buy":
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Cancelling old buy orders",2,symbol)
|
broker.logger.log_this(f"Error canceling old take profit order: {e}",2,symbol)
|
||||||
broker.cancel_order(order["id"],order["symbol"])
|
|
||||||
|
broker.logger.log_this(f"Cancelling old take safety orders",2,symbol)
|
||||||
|
for item in instance.status.get_safety_orders():
|
||||||
|
try:
|
||||||
|
broker.cancel_order(item["id"],item["symbol"])
|
||||||
|
except Exception as e:
|
||||||
|
broker.logger.log_this(f"Error canceling old safety order: {e}",2,symbol)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
running_traders.remove(instance)
|
running_traders.remove(instance)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair())
|
broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair())
|
||||||
add_instance(base,quote)
|
add_instance(base,quote)
|
||||||
return 0
|
return 0
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -276,7 +283,7 @@ def main_routine():
|
||||||
global reload_interval
|
global reload_interval
|
||||||
global screen_buffer
|
global screen_buffer
|
||||||
|
|
||||||
executor = ThreadPoolExecutor(max_workers=len(running_traders)+worker_threads_overprovisioning)
|
executor = ThreadPoolExecutor(max_workers=len(broker.get_config()["pairs"])+worker_threads_overprovisioning)
|
||||||
is_testnet = "TESTNET " if broker.get_config()["is_sandbox"] else ""
|
is_testnet = "TESTNET " if broker.get_config()["is_sandbox"] else ""
|
||||||
exchange_version_label = f"{bright_white}{broker.get_config()['exchange'].upper()} {is_testnet}{white}| DCAv2 {version} | CCXT v{ccxt.__version__}"
|
exchange_version_label = f"{bright_white}{broker.get_config()['exchange'].upper()} {is_testnet}{white}| DCAv2 {version} | CCXT v{ccxt.__version__}"
|
||||||
separator_line = blue + "="*80 + white
|
separator_line = blue + "="*80 + white
|
||||||
|
|
@ -285,12 +292,12 @@ def main_routine():
|
||||||
#Restart traders that have the restart flag raised and remove traders that have the quit flag raised
|
#Restart traders that have the restart flag raised and remove traders that have the quit flag raised
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if instance.restart and instance.config.get_attempt_restart():
|
if instance.restart and instance.config.get_attempt_restart():
|
||||||
broker.logger.log_this(f"Restarting trader",1,instance.config.get_pair())
|
broker.logger.log_this(f"Restarting trader",1,instance.status.get_pair())
|
||||||
restart_pair_no_json(instance.base,instance.quote)
|
restart_pair_no_json(instance.base,instance.quote)
|
||||||
if instance.quit:
|
if instance.quit:
|
||||||
#Here, check if a duster is needed
|
#Here, check if a duster is needed
|
||||||
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader.",0,instance.config.get_pair())
|
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader.",0,instance.status.get_pair())
|
||||||
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader: {instance.config.get_pair()}",-1) #Forced message to TG
|
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader: {instance.status.get_pair()}",-1) #Forced message to TG
|
||||||
if f"{instance.base}{instance.quote}" in tickers:
|
if f"{instance.base}{instance.quote}" in tickers:
|
||||||
tickers.remove(f"{instance.base}{instance.quote}")
|
tickers.remove(f"{instance.base}{instance.quote}")
|
||||||
broker.remove_pair_from_config(f"{instance.base}{instance.quote}")
|
broker.remove_pair_from_config(f"{instance.base}{instance.quote}")
|
||||||
|
|
@ -298,7 +305,7 @@ def main_routine():
|
||||||
try:
|
try:
|
||||||
running_traders.remove(instance)
|
running_traders.remove(instance)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair())
|
broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair())
|
||||||
|
|
||||||
#Adds pending traders
|
#Adds pending traders
|
||||||
if bool(instances_to_add):
|
if bool(instances_to_add):
|
||||||
|
|
@ -310,21 +317,20 @@ def main_routine():
|
||||||
futures = []
|
futures = []
|
||||||
pairs_to_fetch = []
|
pairs_to_fetch = []
|
||||||
online_pairs = []
|
online_pairs = []
|
||||||
open_orders = broker.fetch_open_orders(tickers)
|
|
||||||
|
for instance in running_traders:
|
||||||
|
pairs_to_fetch.append(instance.status.get_pair())
|
||||||
|
|
||||||
|
open_orders = broker.fetch_open_orders(pairs_to_fetch)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
future = executor.submit(instance.check_status, open_orders)
|
future = executor.submit(instance.check_status, open_orders)
|
||||||
futures.append(future)
|
futures.append(future)
|
||||||
online_pairs.append(f"{instance.base}{instance.quote}")
|
online_pairs.append(f"{instance.base}{instance.quote}")
|
||||||
pairs_to_fetch.append(instance.config.get_pair())
|
|
||||||
|
|
||||||
#Delete no longer used data
|
|
||||||
del open_orders
|
|
||||||
|
|
||||||
#Fetch prices
|
#Fetch prices
|
||||||
price_list = broker.get_prices(pairs_to_fetch)
|
price_list = broker.get_prices(pairs_to_fetch)
|
||||||
#Here, assign the prices to the dusters (if any)
|
#Here, assign the prices to the dusters (if any)
|
||||||
|
|
||||||
|
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
|
|
@ -339,21 +345,21 @@ def main_routine():
|
||||||
global_status["paused_traders"].clear()
|
global_status["paused_traders"].clear()
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if not instance.config.get_is_short():
|
if not instance.config.get_is_short():
|
||||||
curr += int(instance.get_status_dict()["so_amount"]) # For the safety order occupancy percentage calculation
|
curr += int(instance.status.get_so_amount()) # For the safety order occupancy percentage calculation
|
||||||
top += int(instance.config.get_no_of_safety_orders()) # It shows the percentage of safety orders not filled
|
top += int(instance.config.get_no_of_safety_orders())
|
||||||
if "status_string" in instance.get_status_dict(): # status_strings
|
if "status_string" in instance.get_status_dict():
|
||||||
long_traders_status_strings.append(str(instance))
|
long_traders_status_strings.append(str(instance))
|
||||||
elif "status_string" in instance.get_status_dict():
|
elif "status_string" in instance.get_status_dict():
|
||||||
short_traders_status_strings.append(str(instance))
|
short_traders_status_strings.append(str(instance))
|
||||||
try:
|
try:
|
||||||
if instance.config.get_pair() in price_list and price_list[instance.config.get_pair()] is not None:
|
if instance.status.get_pair() in price_list and price_list[instance.status.get_pair()] is not None:
|
||||||
instance.get_status_dict()["price"] = price_list[instance.config.get_pair()]
|
instance.get_status_dict()["price"] = price_list[instance.status.get_pair()]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,instance.config.get_pair())
|
broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,instance.status.get_pair())
|
||||||
|
|
||||||
#Add paused traders to the paused trader list
|
#Add paused traders to the paused trader list
|
||||||
if instance.pause:
|
if instance.pause:
|
||||||
global_status["paused_traders"].append(instance.config.get_pair())
|
global_status["paused_traders"].append(instance.status.get_pair())
|
||||||
paused_traders_status_strings.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}")
|
paused_traders_status_strings.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}")
|
||||||
|
|
||||||
#Delete no longer used data
|
#Delete no longer used data
|
||||||
|
|
@ -368,7 +374,7 @@ def main_routine():
|
||||||
|
|
||||||
#Updates some global status variables prior to deletion of those
|
#Updates some global status variables prior to deletion of those
|
||||||
if len(running_traders)!=len(global_status["online_workers"]):
|
if len(running_traders)!=len(global_status["online_workers"]):
|
||||||
global_status["online_workers"] = [instance.config.get_pair() for instance in running_traders]
|
global_status["online_workers"] = [instance.status.get_pair() for instance in running_traders]
|
||||||
|
|
||||||
#Prints general info
|
#Prints general info
|
||||||
instance_uptime = int(time.time()) - instance_start_time
|
instance_uptime = int(time.time()) - instance_start_time
|
||||||
|
|
@ -404,7 +410,7 @@ def main_routine():
|
||||||
#Toggle pauses
|
#Toggle pauses
|
||||||
if toggle_pauses:
|
if toggle_pauses:
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if instance.config.get_pair() in toggle_pauses:
|
if instance.status.get_pair() in toggle_pauses:
|
||||||
instance.pause = not instance.pause
|
instance.pause = not instance.pause
|
||||||
toggle_pauses.clear()
|
toggle_pauses.clear()
|
||||||
|
|
||||||
|
|
@ -690,9 +696,7 @@ def import_pair():
|
||||||
data = request.json
|
data = request.json
|
||||||
base = data["base"]
|
base = data["base"]
|
||||||
quote = data["quote"]
|
quote = data["quote"]
|
||||||
forced_tp_id = data["forced_tp_id"] if "forced_tp_id" in data else None
|
return unwrapped_import_pair(base,quote)
|
||||||
forced_so_id = data["forced_so_id"] if "forced_so_id" in data else None
|
|
||||||
return unwrapped_import_pair(base,quote,forced_tp_id,forced_so_id)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
@ -850,6 +854,58 @@ def mod_order_size():
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
|
@base_api.route("/mod_concurrent_safety_orders", methods=['POST'])
|
||||||
|
def mod_concurrent_safety_orders():
|
||||||
|
'''
|
||||||
|
POST request
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
base: str
|
||||||
|
quote: str
|
||||||
|
amount: int
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||||
|
return jsonify({'Error': 'API key invalid'}), 401
|
||||||
|
try:
|
||||||
|
if request.json is None:
|
||||||
|
return jsonify({'Error': 'request.json is None'})
|
||||||
|
data = request.json
|
||||||
|
base = data["base"]
|
||||||
|
quote = data["quote"]
|
||||||
|
amount = data["amount"]
|
||||||
|
return unwrapped_mod_concurrent_safety_orders(base,quote,amount)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
|
@base_api.route("/mod_boosted_concurrent_safety_orders", methods=['POST'])
|
||||||
|
def mod_boosted_concurrent_safety_orders():
|
||||||
|
'''
|
||||||
|
POST request
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
base: str
|
||||||
|
quote: str
|
||||||
|
amount: int
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||||
|
return jsonify({'Error': 'API key invalid'}), 401
|
||||||
|
try:
|
||||||
|
if request.json is None:
|
||||||
|
return jsonify({'Error': 'request.json is None'})
|
||||||
|
data = request.json
|
||||||
|
base = data["base"]
|
||||||
|
quote = data["quote"]
|
||||||
|
amount = data["amount"]
|
||||||
|
return unwrapped_mod_boosted_concurrent_safety_orders(base,quote,amount)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/mod_default_order_size", methods=['POST'])
|
@base_api.route("/mod_default_order_size", methods=['POST'])
|
||||||
def mod_default_order_size():
|
def mod_default_order_size():
|
||||||
'''
|
'''
|
||||||
|
|
@ -1060,7 +1116,7 @@ def toggle_cleanup():
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/toggle_autoswitch", methods=['POST']) #type:ignore
|
@base_api.route("/toggle_autoswitch", methods=['POST'])
|
||||||
def toggle_autoswitch():
|
def toggle_autoswitch():
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
@ -1084,8 +1140,32 @@ def toggle_autoswitch():
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/toggle_liquidate_after_switch", methods=['POST']) #type:ignore
|
@base_api.route("/force_trader_close", methods=['POST'])
|
||||||
def toggle_liquidate_after_switch(): #type:ignore
|
def force_trader_close():
|
||||||
|
'''
|
||||||
|
POST request
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
base: str
|
||||||
|
quote: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||||
|
return jsonify({'Error': 'API key invalid'}), 401
|
||||||
|
try:
|
||||||
|
if request.json is None:
|
||||||
|
return jsonify({'Error': 'request.json is None'})
|
||||||
|
data = request.json
|
||||||
|
base = data["base"]
|
||||||
|
quote = data["quote"]
|
||||||
|
return unwrapped_force_trader_close(base,quote)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
|
@base_api.route("/toggle_liquidate_after_switch", methods=['POST'])
|
||||||
|
def toggle_liquidate_after_switch():
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
||||||
|
|
@ -1108,7 +1188,7 @@ def toggle_liquidate_after_switch(): #type:ignore
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/toggle_check_old_long_price", methods=['POST'])#type:ignore
|
@base_api.route("/toggle_check_old_long_price", methods=['POST'])
|
||||||
def toggle_check_old_long_price():
|
def toggle_check_old_long_price():
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
@ -1158,6 +1238,20 @@ def switch_quote_currency():
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
|
@base_api.route("/toggle_log_orders", methods=['POST'])
|
||||||
|
def toggle_log_orders():
|
||||||
|
'''
|
||||||
|
POST request
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
None
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||||
|
return jsonify({'Error': 'API key invalid'}), 401
|
||||||
|
return unwrapped_toggle_log_orders()
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/toggle_restart", methods=['POST'])
|
@base_api.route("/toggle_restart", methods=['POST'])
|
||||||
def toggle_restart():
|
def toggle_restart():
|
||||||
'''
|
'''
|
||||||
|
|
@ -1214,6 +1308,21 @@ def get_log_list():
|
||||||
return unwrapped_get_log_list()
|
return unwrapped_get_log_list()
|
||||||
|
|
||||||
|
|
||||||
|
@base_api.route("/refresh_log_cache", methods=['POST'])
|
||||||
|
def refresh_log_cache():
|
||||||
|
'''
|
||||||
|
POST request
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||||
|
return jsonify({'Error': 'API key invalid'}), 401
|
||||||
|
try:
|
||||||
|
return unwrapped_refresh_log_cache()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/get_balance", methods=['GET'])
|
@base_api.route("/get_balance", methods=['GET'])
|
||||||
def get_balance():
|
def get_balance():
|
||||||
'''
|
'''
|
||||||
|
|
@ -1338,30 +1447,7 @@ def reload_markets():
|
||||||
return unwrapped_reload_markets()
|
return unwrapped_reload_markets()
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/reload_safety_order", methods=['POST'])
|
@base_api.route("/reload_trader_config", methods=['POST'])
|
||||||
def reload_safety_order():
|
|
||||||
'''
|
|
||||||
POST request
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
None
|
|
||||||
'''
|
|
||||||
|
|
||||||
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
|
||||||
return jsonify({'Error': 'API key invalid'}), 401
|
|
||||||
try:
|
|
||||||
if request.json is None:
|
|
||||||
return jsonify({'Error': 'request.json is None'})
|
|
||||||
data = request.json
|
|
||||||
base = data["base"]
|
|
||||||
quote = data["quote"]
|
|
||||||
return unwrapped_reload_safety_order(base,quote)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return jsonify({'Error': 'Halp'})
|
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/reload_trader_config", methods=['POST'])#type:ignore
|
|
||||||
def reload_trader_config():
|
def reload_trader_config():
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
@ -1453,7 +1539,7 @@ def unwrapped_add_pair(base,quote):
|
||||||
|
|
||||||
#Check if the trader is already running
|
#Check if the trader is already running
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
broker.logger.log_this(f"Pair already running",1,symbol)
|
broker.logger.log_this(f"Pair already running",1,symbol)
|
||||||
return jsonify({"Error": "Pair already running"})
|
return jsonify({"Error": "Pair already running"})
|
||||||
|
|
||||||
|
|
@ -1487,7 +1573,7 @@ def unwrapped_remove_pair(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.quit = True
|
instance.quit = True
|
||||||
return jsonify({"Success": "Pair to be removed"})
|
return jsonify({"Success": "Pair to be removed"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1512,15 +1598,13 @@ def unwrapped_restart_pair(base,quote):
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_import_pair(base,quote,forced_tp_id = None, forced_so_id = None):
|
def unwrapped_import_pair(base,quote):
|
||||||
'''
|
'''
|
||||||
Imports a previously running pair
|
Imports a previously running pair
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
base (str): The base currency of the pair
|
base (str): The base currency of the pair
|
||||||
quote (str): The quote currency of the pair
|
quote (str): The quote currency of the pair
|
||||||
forced_tp_id (str): The ID of the take profit order to use
|
|
||||||
forced_so_id (str): The ID of the stop order to use
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
jsonified dictionary detailing the outcome of the operation.
|
jsonified dictionary detailing the outcome of the operation.
|
||||||
|
|
@ -1528,7 +1612,7 @@ def unwrapped_import_pair(base,quote,forced_tp_id = None, forced_so_id = None):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
import_instance(base,quote,forced_tp_id,forced_so_id)
|
import_instance(base,quote)
|
||||||
broker.add_pair_to_config(f"{base}{quote}")
|
broker.add_pair_to_config(f"{base}{quote}")
|
||||||
broker.rewrite_config_file()
|
broker.rewrite_config_file()
|
||||||
broker.logger.log_this(f"Done",2,symbol)
|
broker.logger.log_this(f"Done",2,symbol)
|
||||||
|
|
@ -1556,9 +1640,9 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
|
||||||
if f"{base}{quote}" not in broker.get_pairs():
|
if f"{base}{quote}" not in broker.get_pairs():
|
||||||
return jsonify({"Error": "Pair not running"})
|
return jsonify({"Error": "Pair not running"})
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if f"{base}/{quote}"==instance.config.get_pair():
|
if f"{base}/{quote}"==instance.status.get_pair():
|
||||||
instance.pause = True
|
instance.set_pause(True, "Switching to long mode")
|
||||||
if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
|
if instance.switch_to_long(ignore_old_long=ignore_old_long,double_check_price=False)==1:
|
||||||
return jsonify({"Error": "Error in switch_to_long()"})
|
return jsonify({"Error": "Error in switch_to_long()"})
|
||||||
if instance.start_trader()==1:
|
if instance.start_trader()==1:
|
||||||
instance.quit = True
|
instance.quit = True
|
||||||
|
|
@ -1584,14 +1668,14 @@ def unwrapped_switch_to_short(base,quote):
|
||||||
if f"{base}{quote}" not in broker.get_pairs():
|
if f"{base}{quote}" not in broker.get_pairs():
|
||||||
return jsonify({"Error": "Pair not running"})
|
return jsonify({"Error": "Pair not running"})
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair() and instance.switch_to_short()==1:
|
if symbol==instance.status.get_pair() and instance.switch_to_short()==1:
|
||||||
return jsonify({"Error": "Error in switch_to_short()"})
|
return jsonify({"Error": "Error in switch_to_short()"})
|
||||||
|
|
||||||
#Restart instance
|
#Restart instance
|
||||||
try:
|
try:
|
||||||
broker.logger.log_this(f"Reinitializing trader",2,symbol)
|
broker.logger.log_this(f"Reinitializing trader",2,symbol)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.status.set_take_profit_order(instance.broker.empty_order)
|
instance.status.set_take_profit_order(instance.broker.empty_order)
|
||||||
instance.so = instance.broker.empty_order
|
instance.so = instance.broker.empty_order
|
||||||
|
|
||||||
|
|
@ -1640,7 +1724,7 @@ def unwrapped_load_old_long(base,quote):
|
||||||
|
|
||||||
#Creates (or modifies) a key in the status dictionary and assigns the contents of the file to that same key.
|
#Creates (or modifies) a key in the status dictionary and assigns the contents of the file to that same key.
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if instance.config.get_pair()==symbol:
|
if instance.status.get_pair()==symbol:
|
||||||
instance.get_status_dict()["old_long"]=old_long
|
instance.get_status_dict()["old_long"]=old_long
|
||||||
instance.update_status(True)
|
instance.update_status(True)
|
||||||
return jsonify({"Success": "old_long file loaded to status_dict"})
|
return jsonify({"Success": "old_long file loaded to status_dict"})
|
||||||
|
|
@ -1667,7 +1751,7 @@ def unwrapped_view_old_long(base,quote,from_file):
|
||||||
old_long = load(ol)
|
old_long = load(ol)
|
||||||
return jsonify(old_long)
|
return jsonify(old_long)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
if "old_long" in instance.get_status_dict():
|
if "old_long" in instance.get_status_dict():
|
||||||
return jsonify(instance.get_status_dict()["old_long"])
|
return jsonify(instance.get_status_dict()["old_long"])
|
||||||
return jsonify({"Error": "No old_long info found"})
|
return jsonify({"Error": "No old_long info found"})
|
||||||
|
|
@ -1693,7 +1777,7 @@ def unwrapped_switch_to_long_price(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
if "old_long" in instance.get_status_dict():
|
if "old_long" in instance.get_status_dict():
|
||||||
#minimum_switch_price = (old_target - quote_already_in)/base_left
|
#minimum_switch_price = (old_target - quote_already_in)/base_left
|
||||||
old_target = instance.get_status_dict()["old_long"]["tp_price"]*instance.get_status_dict()["old_long"]["tp_amount"]
|
old_target = instance.get_status_dict()["old_long"]["tp_price"]*instance.get_status_dict()["old_long"]["tp_amount"]
|
||||||
|
|
@ -1724,15 +1808,14 @@ def unwrapped_add_safety_orders(base,quote,amount):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.pause = True
|
instance.set_pause(True, "Adding safety orders")
|
||||||
#x.no_of_safety_orders += int(amount)
|
instance.status.set_no_of_safety_orders(instance.status.get_no_of_safety_orders()+int(amount))
|
||||||
instance.config.set_no_of_safety_orders(instance.config.get_no_of_safety_orders()+int(amount))
|
|
||||||
broker.logger.log_this("Recalculating safety price table...",1,symbol)
|
broker.logger.log_this("Recalculating safety price table...",1,symbol)
|
||||||
instance.status.set_safety_price_table(instance.calculate_safety_prices(instance.status.get_start_price(),instance.config.get_no_of_safety_orders(),instance.config.get_safety_order_deviance()))
|
instance.status.set_safety_price_table(instance.calculate_safety_prices(instance.status.get_start_price(),instance.config.get_no_of_safety_orders(),instance.config.get_safety_order_deviance()))
|
||||||
broker.logger.log_this(f"Done. Added {amount} safety orders",1,symbol)
|
broker.logger.log_this(f"Done. Added {amount} safety orders",1,symbol)
|
||||||
instance.update_status(True)
|
instance.update_status(True)
|
||||||
instance.pause = False
|
instance.set_pause(False)
|
||||||
return jsonify({"Success": f"Done. Added {amount} safety orders"})
|
return jsonify({"Success": f"Done. Added {amount} safety orders"})
|
||||||
return jsonify({"Error": "Pair not found"})
|
return jsonify({"Error": "Pair not found"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1755,7 +1838,7 @@ def unwrapped_base_add_so_calculation(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
free_base = instance.fetch_free_base()
|
free_base = instance.fetch_free_base()
|
||||||
if free_base is None:
|
if free_base is None:
|
||||||
return jsonify({"Error": "Can't fetch amount of free base on the exchange"})
|
return jsonify({"Error": "Can't fetch amount of free base on the exchange"})
|
||||||
|
|
@ -1783,7 +1866,7 @@ def unwrapped_mod_tp_level(base,quote,amount):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.config.set_tp_level(float(amount))
|
instance.config.set_tp_level(float(amount))
|
||||||
broker.logger.log_this("Done. The change will take effect when the next take profit order is placed",2,symbol)
|
broker.logger.log_this("Done. The change will take effect when the next take profit order is placed",2,symbol)
|
||||||
return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"})
|
return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"})
|
||||||
|
|
@ -1808,8 +1891,9 @@ def unwrapped_mod_order_size(base,quote,amount):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.config.set_order_size(float(amount))
|
instance.config.set_order_size(float(amount))
|
||||||
|
instance.config.save_to_file()
|
||||||
broker.logger.log_this("Done. The change will take effect when the next deal is started",2,symbol)
|
broker.logger.log_this("Done. The change will take effect when the next deal is started",2,symbol)
|
||||||
return jsonify({"Success": "Success. The change will take effect when the next deal is started"})
|
return jsonify({"Success": "Success. The change will take effect when the next deal is started"})
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -1817,6 +1901,58 @@ def unwrapped_mod_order_size(base,quote,amount):
|
||||||
return jsonify({"Error": "Error changing order size"})
|
return jsonify({"Error": "Error changing order size"})
|
||||||
|
|
||||||
|
|
||||||
|
def unwrapped_mod_concurrent_safety_orders(base,quote,amount):
|
||||||
|
'''
|
||||||
|
Modifies the amount of safety orders that a trader keeps opened at the same time.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
base (str): The base currency of the pair.
|
||||||
|
quote (str): The quote currency of the pair.
|
||||||
|
amount (str): The new amount.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
jsonify: A jsonified dictionary detailing the outcome of the operation
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
symbol = f"{base}/{quote}"
|
||||||
|
for instance in running_traders:
|
||||||
|
if symbol==instance.status.get_pair():
|
||||||
|
instance.config.set_concurrent_safety_orders(int(amount))
|
||||||
|
instance.config.save_to_file()
|
||||||
|
broker.logger.log_this("Done. The change will take effect as new safety orders are sent or filled",2,symbol)
|
||||||
|
return jsonify({"Success": "Success. The change will take effect as new safety orders are sent or filled"})
|
||||||
|
except Exception:
|
||||||
|
broker.logger.log_this("Error changing safety orders amount. Ignoring...",2,symbol)
|
||||||
|
return jsonify({"Error": "Error changing safety orders amount"})
|
||||||
|
|
||||||
|
|
||||||
|
def unwrapped_mod_boosted_concurrent_safety_orders(base,quote,amount):
|
||||||
|
'''
|
||||||
|
Modifies the amount of safety orders that a trader keeps opened at the same time while boosted.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
base (str): The base currency of the pair.
|
||||||
|
quote (str): The quote currency of the pair.
|
||||||
|
amount (str): The new amount.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
jsonify: A jsonified dictionary detailing the outcome of the operation
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
symbol = f"{base}/{quote}"
|
||||||
|
for instance in running_traders:
|
||||||
|
if symbol==instance.status.get_pair():
|
||||||
|
instance.config.set_boosted_concurrent_safety_orders(int(amount))
|
||||||
|
instance.config.save_to_file()
|
||||||
|
broker.logger.log_this("Done. The change will take effect as new safety orders are sent or filled",2,symbol)
|
||||||
|
return jsonify({"Success": "Success. The change will take effect as new safety orders are sent or filled"})
|
||||||
|
except Exception:
|
||||||
|
broker.logger.log_this("Error changing safety orders amount. Ignoring...",2,symbol)
|
||||||
|
return jsonify({"Error": "Error changing safety orders amount"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_mod_default_order_size(amount):
|
def unwrapped_mod_default_order_size(amount):
|
||||||
'''
|
'''
|
||||||
Modifies the default order size of a broker.
|
Modifies the default order size of a broker.
|
||||||
|
|
@ -1872,7 +2008,7 @@ def unwrapped_last_call(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.status.set_stop_when_profit(not instance.status.get_stop_when_profit())
|
instance.status.set_stop_when_profit(not instance.status.get_stop_when_profit())
|
||||||
instance.update_status(True)
|
instance.update_status(True)
|
||||||
if instance.status.get_stop_when_profit():
|
if instance.status.get_stop_when_profit():
|
||||||
|
|
@ -1906,7 +2042,7 @@ def unwrapped_deferred_last_call(base,quote,yyyymmdd):
|
||||||
if limit==0:
|
if limit==0:
|
||||||
return jsonify({"Error": "Can't convert date to unix"})
|
return jsonify({"Error": "Can't convert date to unix"})
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if f"{base}{quote}"==instance.config.get_pair():
|
if f"{base}{quote}"==instance.status.get_pair():
|
||||||
instance.config.set_programmed_stop_time(limit)
|
instance.config.set_programmed_stop_time(limit)
|
||||||
instance.config.set_programmed_stop(True)
|
instance.config.set_programmed_stop(True)
|
||||||
#save config file to disk
|
#save config file to disk
|
||||||
|
|
@ -1933,7 +2069,7 @@ def unwrapped_toggle_pause(base,quote):
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
toggle_pauses.append(symbol)
|
toggle_pauses.append(symbol)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if instance.config.get_pair()==symbol:
|
if instance.status.get_pair()==symbol:
|
||||||
if instance.pause:
|
if instance.pause:
|
||||||
instance.status.set_pause_reason("")
|
instance.status.set_pause_reason("")
|
||||||
return jsonify({"Success": "Trader will be resumed"})
|
return jsonify({"Success": "Trader will be resumed"})
|
||||||
|
|
@ -1952,14 +2088,11 @@ def unwrapped_global_last_call():
|
||||||
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
if broker.get_pairs!=[]:
|
for instance in running_traders:
|
||||||
#broker.clear_pairs()
|
instance.status.set_stop_when_profit(True)
|
||||||
for instance in running_traders:
|
instance.config.set_autoswitch(False)
|
||||||
instance.status.set_stop_when_profit(True)
|
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
|
||||||
instance.config.set_autoswitch(False)
|
return jsonify({"Success": "All traders scheduled to go offline when profit is reached"})
|
||||||
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
|
|
||||||
return jsonify({"Success": "All traders scheduled to go offline when profit is reached"})
|
|
||||||
return jsonify({"Error": "No traders running"})
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
@ -1972,13 +2105,10 @@ def unwrapped_cancel_global_last_call():
|
||||||
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
if broker.get_pairs!=[]:
|
for instance in running_traders:
|
||||||
#broker.clear_pairs()
|
instance.status.set_stop_when_profit(False)
|
||||||
for instance in running_traders:
|
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
|
||||||
instance.status.set_stop_when_profit(False)
|
return jsonify({"Success": "Last call canceled"})
|
||||||
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
|
|
||||||
return jsonify({"Success": "Last call canceled"})
|
|
||||||
return jsonify({"Error": "No traders running"})
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
@ -1998,55 +2128,55 @@ def unwrapped_add_quote(base,quote,amount):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if f"{base}/{quote}"==instance.config.get_pair():
|
if f"{base}/{quote}"==instance.status.get_pair():
|
||||||
if instance.config.get_is_short():
|
if instance.config.get_is_short():
|
||||||
return jsonify({"Error": "Quote can't be added to short traders"})
|
return jsonify({"Error": "Quote can't be added to short traders"})
|
||||||
instance.pause = True
|
instance.set_pause(True, "Adding quote")
|
||||||
new_average_price = (instance.status.get_quote_spent()+float(amount))/(instance.status.get_base_bought()+(float(amount)/instance.status.get_price()))
|
new_average_price = (instance.status.get_quote_spent()+float(amount))/(instance.status.get_base_bought()+(float(amount)/instance.status.get_price()))
|
||||||
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {quote}",2,instance.config.get_pair())
|
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {quote}",2,instance.status.get_pair())
|
||||||
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*instance.get_tp_level()} {quote}",2,instance.config.get_pair())
|
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*instance.get_tp_level()} {quote}",2,instance.status.get_pair())
|
||||||
new_order = broker.new_market_order(instance.config.get_pair(),float(amount),"buy")
|
new_order = broker.new_market_order(instance.status.get_pair(),float(amount),"buy")
|
||||||
if new_order is None:
|
if new_order is None:
|
||||||
broker.logger.log_this("Error: Market order returned None",2,instance.config.get_pair())
|
broker.logger.log_this("Error: Market order returned None",2,instance.status.get_pair())
|
||||||
instance.pause = False
|
instance.set_pause(False)
|
||||||
return jsonify({"Error": "Market order returned None"})
|
return jsonify({"Error": "Market order returned None"})
|
||||||
while True:
|
while True:
|
||||||
time.sleep(broker.get_wait_time())
|
time.sleep(broker.get_wait_time())
|
||||||
returned_order = broker.get_order(new_order["id"],instance.config.get_pair())
|
returned_order = broker.get_order(new_order["id"],instance.status.get_pair())
|
||||||
if returned_order==broker.empty_order:
|
if returned_order==broker.empty_order:
|
||||||
broker.logger.log_this("Problems sending the order",2,instance.config.get_pair())
|
broker.logger.log_this("Problems sending the order",2,instance.status.get_pair())
|
||||||
instance.pause = False
|
instance.set_pause(False)
|
||||||
return jsonify({"Error": "Problems sending the order"})
|
return jsonify({"Error": "Problems sending the order"})
|
||||||
elif returned_order["status"]=="expired":
|
elif returned_order["status"]=="expired":
|
||||||
instance.pause = False
|
instance.set_pause(False)
|
||||||
return jsonify({"Error": "New order expired"})
|
return jsonify({"Error": "New order expired"})
|
||||||
elif returned_order["status"]=="closed":
|
elif returned_order["status"]=="closed":
|
||||||
broker.logger.log_this("Order sent",2,instance.config.get_pair())
|
broker.logger.log_this("Order sent",2,instance.status.get_pair())
|
||||||
new_fees_in_base, new_fees_in_quote = instance.parse_fees(returned_order)
|
new_fees_in_base, new_fees_in_quote = instance.parse_fees(returned_order)
|
||||||
instance.status.set_fees_paid_in_base(instance.status.get_fees_paid_in_base() + new_fees_in_base)
|
instance.status.set_fees_paid_in_base(instance.status.get_fees_paid_in_base() + new_fees_in_base)
|
||||||
instance.status.set_fees_paid_in_quote(instance.status.get_fees_paid_in_quote() + new_fees_in_quote)
|
instance.status.set_fees_paid_in_quote(instance.status.get_fees_paid_in_quote() + new_fees_in_quote)
|
||||||
instance.status.set_base_bought(instance.status.get_base_bought() + returned_order["filled"] - new_fees_in_base)
|
instance.status.set_base_bought(instance.status.get_base_bought() + returned_order["filled"] - new_fees_in_base)
|
||||||
instance.status.set_quote_spent(instance.status.get_quote_spent()+returned_order["cost"])
|
instance.status.set_quote_spent(instance.status.get_quote_spent()+returned_order["cost"])
|
||||||
broker.logger.log_this("Cancelling old take profit order and sending a new one",2,instance.config.get_pair())
|
broker.logger.log_this("Cancelling old take profit order and sending a new one",2,instance.status.get_pair())
|
||||||
attempts = 5
|
attempts = 5
|
||||||
while broker.cancel_order(instance.status.get_take_profit_order()["id"],instance.config.get_pair())==1:
|
while broker.cancel_order(instance.status.get_take_profit_order()["id"],instance.status.get_pair())==1:
|
||||||
broker.logger.log_this("Can't cancel old take profit order, retrying...",2,instance.config.get_pair())
|
broker.logger.log_this("Can't cancel old take profit order, retrying...",2,instance.status.get_pair())
|
||||||
time.sleep(broker.get_wait_time())
|
time.sleep(broker.get_wait_time())
|
||||||
attempts-=1
|
attempts-=1
|
||||||
if attempts==0:
|
if attempts==0:
|
||||||
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.config.get_pair())
|
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.status.get_pair())
|
||||||
instance.pause = False
|
instance.set_pause(False)
|
||||||
return jsonify({"Error": "Can't cancel old take profit order."})
|
return jsonify({"Error": "Can't cancel old take profit order."})
|
||||||
instance.status.set_take_profit_price(instance.status.get_quote_spent()/instance.status.get_base_bought()*instance.get_tp_level())
|
instance.status.set_take_profit_price(instance.status.get_quote_spent()/instance.status.get_base_bought()*instance.get_tp_level())
|
||||||
instance.status.set_take_profit_order(broker.new_limit_order(instance.config.get_pair(),instance.status.get_base_bought(),"sell",instance.status.get_take_profit_price()))
|
instance.status.set_take_profit_order(broker.new_limit_order(instance.status.get_pair(),instance.status.get_base_bought(),"sell",instance.status.get_take_profit_price()))
|
||||||
instance.update_status(True)
|
instance.update_status(True)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
broker.logger.log_this("Waiting for initial order to get filled",2,instance.config.get_pair())
|
broker.logger.log_this("Waiting for initial order to get filled",2,instance.status.get_pair())
|
||||||
broker.logger.log_this(f"{returned_order}",2,instance.config.get_pair())
|
broker.logger.log_this(f"{returned_order}",2,instance.status.get_pair())
|
||||||
time.sleep(broker.get_wait_time())
|
time.sleep(broker.get_wait_time())
|
||||||
instance.pause = False
|
instance.set_pause(False)
|
||||||
broker.logger.log_this("Done",2,instance.config.get_pair())
|
broker.logger.log_this("Done",2,instance.status.get_pair())
|
||||||
return jsonify({"Success": "Quote added successfully"})
|
return jsonify({"Success": "Quote added successfully"})
|
||||||
return jsonify({"Error": "Something horrible happened :S"})
|
return jsonify({"Error": "Something horrible happened :S"})
|
||||||
|
|
||||||
|
|
@ -2084,7 +2214,7 @@ def unwrapped_toggle_cleanup(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
instance.config.set_cleanup(not instance.config.get_cleanup())
|
instance.config.set_cleanup(not instance.config.get_cleanup())
|
||||||
if instance.config.get_cleanup():
|
if instance.config.get_cleanup():
|
||||||
return jsonify({"Success": "Cleanup turned ON"})
|
return jsonify({"Success": "Cleanup turned ON"})
|
||||||
|
|
@ -2095,6 +2225,32 @@ def unwrapped_toggle_cleanup(base,quote):
|
||||||
return jsonify({"Error": "Task failed successfully"})
|
return jsonify({"Error": "Task failed successfully"})
|
||||||
|
|
||||||
|
|
||||||
|
def unwrapped_force_trader_close(base,quote):
|
||||||
|
'''
|
||||||
|
Forces a trader to close the position.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
base (str): The base currency of the pair to close
|
||||||
|
quote (str): The quote currency of the pair to close
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
symbol = f"{base}/{quote}"
|
||||||
|
for instance in running_traders:
|
||||||
|
if symbol==instance.status.get_pair():
|
||||||
|
outcome = instance.force_close()
|
||||||
|
if outcome==0:
|
||||||
|
return jsonify({"Success": "Trader closed position successfully"})
|
||||||
|
return jsonify({"Error": "Error while forcing trader to close position"})
|
||||||
|
return jsonify({"Error": "Trader not found"})
|
||||||
|
except Exception as e:
|
||||||
|
broker.logger.log_this(f"Exception while forcing trader to close position: {e}",1,symbol)
|
||||||
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_toggle_autoswitch(base,quote):
|
def unwrapped_toggle_autoswitch(base,quote):
|
||||||
'''
|
'''
|
||||||
Signals a trader to enable or disable autoswitch.
|
Signals a trader to enable or disable autoswitch.
|
||||||
|
|
@ -2110,7 +2266,7 @@ def unwrapped_toggle_autoswitch(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
if instance.config.get_autoswitch():
|
if instance.config.get_autoswitch():
|
||||||
broker.logger.log_this("Autoswitch turned OFF",1,symbol)
|
broker.logger.log_this("Autoswitch turned OFF",1,symbol)
|
||||||
instance.config.set_autoswitch(False)
|
instance.config.set_autoswitch(False)
|
||||||
|
|
@ -2119,10 +2275,12 @@ def unwrapped_toggle_autoswitch(base,quote):
|
||||||
broker.logger.log_this("Autoswitch turned ON",1,symbol)
|
broker.logger.log_this("Autoswitch turned ON",1,symbol)
|
||||||
instance.config.set_autoswitch(True)
|
instance.config.set_autoswitch(True)
|
||||||
return jsonify({"Success": "Autoswitch is now ON"})
|
return jsonify({"Success": "Autoswitch is now ON"})
|
||||||
|
return jsonify({"Error": "Trader not running"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol)
|
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol)
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_toggle_liquidate_after_switch(base,quote):
|
def unwrapped_toggle_liquidate_after_switch(base,quote):
|
||||||
'''
|
'''
|
||||||
Signals a trader to enable or disable quitting after switching from short to long.
|
Signals a trader to enable or disable quitting after switching from short to long.
|
||||||
|
|
@ -2138,7 +2296,7 @@ def unwrapped_toggle_liquidate_after_switch(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
if instance.config.get_liquidate_after_switch():
|
if instance.config.get_liquidate_after_switch():
|
||||||
broker.logger.log_this("Liquidate after switch turned OFF",1,symbol)
|
broker.logger.log_this("Liquidate after switch turned OFF",1,symbol)
|
||||||
instance.config.set_liquidate_after_switch(False)
|
instance.config.set_liquidate_after_switch(False)
|
||||||
|
|
@ -2147,10 +2305,12 @@ def unwrapped_toggle_liquidate_after_switch(base,quote):
|
||||||
broker.logger.log_this("Liquidate after switch turned ON",1,symbol)
|
broker.logger.log_this("Liquidate after switch turned ON",1,symbol)
|
||||||
instance.config.set_liquidate_after_switch(True)
|
instance.config.set_liquidate_after_switch(True)
|
||||||
return jsonify({"Success": "Liquidate after switch is now ON"})
|
return jsonify({"Success": "Liquidate after switch is now ON"})
|
||||||
|
return jsonify({"Error": "Trader not running"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while toggling liquidate after switch: {e}",1,symbol)
|
broker.logger.log_this(f"Exception while toggling liquidate after switch: {e}",1,symbol)
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_toggle_check_old_long_price(base,quote):
|
def unwrapped_toggle_check_old_long_price(base,quote):
|
||||||
'''
|
'''
|
||||||
Signals to the trader if it should compare the current price to the old_long price stored in the old_long dictionary.
|
Signals to the trader if it should compare the current price to the old_long price stored in the old_long dictionary.
|
||||||
|
|
@ -2166,7 +2326,7 @@ def unwrapped_toggle_check_old_long_price(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.config.get_pair():
|
if symbol==instance.status.get_pair():
|
||||||
if instance.config.get_check_old_long_price():
|
if instance.config.get_check_old_long_price():
|
||||||
broker.logger.log_this("Check OFF",1,symbol)
|
broker.logger.log_this("Check OFF",1,symbol)
|
||||||
instance.config.set_check_old_long_price(False)
|
instance.config.set_check_old_long_price(False)
|
||||||
|
|
@ -2175,6 +2335,7 @@ def unwrapped_toggle_check_old_long_price(base,quote):
|
||||||
broker.logger.log_this("Check ON",1,symbol)
|
broker.logger.log_this("Check ON",1,symbol)
|
||||||
instance.config.set_check_old_long_price(True)
|
instance.config.set_check_old_long_price(True)
|
||||||
return jsonify({"Success": "Old long price check turned ON"})
|
return jsonify({"Success": "Old long price check turned ON"})
|
||||||
|
return jsonify({"Error": "Trader not running"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while toggling check_old_long_price: {e}",1,symbol)
|
broker.logger.log_this(f"Exception while toggling check_old_long_price: {e}",1,symbol)
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
@ -2196,16 +2357,16 @@ def unwrapped_switch_quote_currency(base,quote,new_quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for trader in running_traders:
|
for trader in running_traders:
|
||||||
if symbol==trader.config.get_pair():
|
if symbol==trader.status.get_pair():
|
||||||
#Pause the trader
|
#Pause the trader
|
||||||
trader.pause = True
|
trader.set_pause(True, "Switching quote currency")
|
||||||
|
|
||||||
#Call x.switch_quote_currency
|
#Call x.switch_quote_currency
|
||||||
if trader.switch_quote_currency(new_quote)==1:
|
if trader.switch_quote_currency(new_quote)==1:
|
||||||
return jsonify({"Error": "Swap failed. Check log files for details."})
|
return jsonify({"Error": "Swap failed. Check log files for details."})
|
||||||
|
|
||||||
#Resume the trader
|
#Resume the trader
|
||||||
trader.pause = False
|
trader.set_pause(False)
|
||||||
return jsonify({"Success": "Mission successful"})
|
return jsonify({"Success": "Mission successful"})
|
||||||
return jsonify({"Error": "Trader not found"})
|
return jsonify({"Error": "Trader not found"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -2228,6 +2389,20 @@ def unwrapped_toggle_restart():
|
||||||
return jsonify({"Success": "attempt_to_restart disabled"})
|
return jsonify({"Success": "attempt_to_restart disabled"})
|
||||||
|
|
||||||
|
|
||||||
|
def unwrapped_toggle_log_orders():
|
||||||
|
'''
|
||||||
|
Toggles on or off the logging of orders.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
||||||
|
'''
|
||||||
|
|
||||||
|
broker.set_log_orders(not broker.get_log_orders())
|
||||||
|
if broker.get_log_orders():
|
||||||
|
return jsonify({"Success": "log_orders enabled"})
|
||||||
|
return jsonify({"Success": "log_orders disabled"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_toggle_telegram():
|
def unwrapped_toggle_telegram():
|
||||||
'''
|
'''
|
||||||
Switches on or off the Telegram notifications
|
Switches on or off the Telegram notifications
|
||||||
|
|
@ -2299,6 +2474,22 @@ def unwrapped_get_log_list():
|
||||||
return jsonify({"Logs": broker.logger.get_log_list()})
|
return jsonify({"Logs": broker.logger.get_log_list()})
|
||||||
|
|
||||||
|
|
||||||
|
def unwrapped_refresh_log_cache():
|
||||||
|
'''
|
||||||
|
Reloads the log file cache.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
jsonify: A jsonified dictionary containing the last n entries from the log file.
|
||||||
|
'''
|
||||||
|
if broker.logger.refresh_logs()==0:
|
||||||
|
return jsonify({"Success": "Logs refreshed"})
|
||||||
|
else:
|
||||||
|
return jsonify({"Error": "Error while refreshing logs"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_get_deals_cache():
|
def unwrapped_get_deals_cache():
|
||||||
'''
|
'''
|
||||||
Retrieves the last n entries from the broker's logger.
|
Retrieves the last n entries from the broker's logger.
|
||||||
|
|
@ -2359,30 +2550,7 @@ def unwrapped_reload_markets():
|
||||||
return jsonify({"Success": "Markets reloaded successfully"})
|
return jsonify({"Success": "Markets reloaded successfully"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while reloading markets: {e}",1)
|
broker.logger.log_this(f"Exception while reloading markets: {e}",1)
|
||||||
return jsonify({"Error": "Markets couldn't be reloaded"})
|
return jsonify({"Error": "Markets couldn't be reloaded"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_reload_safety_order(base,quote):
|
|
||||||
'''
|
|
||||||
Reloads the safety order of a trader.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
base (str): The base currency of the trader.
|
|
||||||
quote (str): The quote currency of the trader.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
symbol = f"{base}/{quote}"
|
|
||||||
for trader in running_traders:
|
|
||||||
if trader.config.get_pair()==symbol:
|
|
||||||
trader.config.load_from_file()
|
|
||||||
return jsonify({"Success": "Safety order reloaded successfully"})
|
|
||||||
return jsonify({"Error": "Trader not found"})
|
|
||||||
except Exception as e:
|
|
||||||
broker.logger.log_this(f"Exception while reloading safety order: {e}",1,symbol)
|
|
||||||
return jsonify({"Error": "Safety order couldn't be reloaded"})
|
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_get_balance(coin):
|
def unwrapped_get_balance(coin):
|
||||||
|
|
@ -2419,7 +2587,7 @@ def unwrapped_reload_trader_config(base,quote):
|
||||||
'''
|
'''
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for trader in running_traders:
|
for trader in running_traders:
|
||||||
if trader.config.get_pair() == symbol:
|
if trader.status.get_pair() == symbol:
|
||||||
if trader.config.load_from_file()==0:
|
if trader.config.load_from_file()==0:
|
||||||
return jsonify({"Success": "Config file reloaded"})
|
return jsonify({"Success": "Config file reloaded"})
|
||||||
return jsonify({"Error": "Error reloading config file"})
|
return jsonify({"Error": "Error reloading config file"})
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ class StatusHandler:
|
||||||
"pair": f"{base}/{quote}",
|
"pair": f"{base}/{quote}",
|
||||||
"take_profit_order": broker.get_empty_order(),
|
"take_profit_order": broker.get_empty_order(),
|
||||||
"take_profit_price": 0.0,
|
"take_profit_price": 0.0,
|
||||||
"safety_order": broker.get_empty_order(),
|
"safety_orders": [],
|
||||||
|
"safety_orders_filled": 0,
|
||||||
"next_so_price": 0.0,
|
"next_so_price": 0.0,
|
||||||
"order_size": 0.0,
|
"order_size": 0.0,
|
||||||
"partial_profit": 0.0,
|
"partial_profit": 0.0,
|
||||||
|
|
@ -23,7 +24,7 @@ class StatusHandler:
|
||||||
"quote_spent": 0.0,
|
"quote_spent": 0.0,
|
||||||
"base_bought": 0.0,
|
"base_bought": 0.0,
|
||||||
"so_amount": 0,
|
"so_amount": 0,
|
||||||
"no_of_safety_orders": "",
|
"no_of_safety_orders": 0,
|
||||||
"safety_price_table": [],
|
"safety_price_table": [],
|
||||||
"deal_uptime": 0.0,
|
"deal_uptime": 0.0,
|
||||||
"total_uptime": 0.0,
|
"total_uptime": 0.0,
|
||||||
|
|
@ -58,8 +59,14 @@ class StatusHandler:
|
||||||
def get_take_profit_price(self):
|
def get_take_profit_price(self):
|
||||||
return self.status_dictionary["take_profit_price"]
|
return self.status_dictionary["take_profit_price"]
|
||||||
|
|
||||||
def get_safety_order(self):
|
def get_safety_orders(self):
|
||||||
return self.status_dictionary["safety_order"]
|
"""
|
||||||
|
Returns the list of open safety orders
|
||||||
|
"""
|
||||||
|
return self.status_dictionary["safety_orders"]
|
||||||
|
|
||||||
|
def get_safety_orders_filled(self):
|
||||||
|
return self.status_dictionary["safety_orders_filled"]
|
||||||
|
|
||||||
def get_next_so_price(self):
|
def get_next_so_price(self):
|
||||||
return self.status_dictionary["next_so_price"]
|
return self.status_dictionary["next_so_price"]
|
||||||
|
|
@ -148,6 +155,10 @@ class StatusHandler:
|
||||||
def get_status_file_path(self):
|
def get_status_file_path(self):
|
||||||
return self.status_file_path
|
return self.status_file_path
|
||||||
|
|
||||||
|
def set_pair(self, trading_pair):
|
||||||
|
self.pair = trading_pair
|
||||||
|
return 0
|
||||||
|
|
||||||
def set_status_file_path(self, new_file_path: str):
|
def set_status_file_path(self, new_file_path: str):
|
||||||
# if not isinstance(new_file_path, str):
|
# if not isinstance(new_file_path, str):
|
||||||
# self.broker.logger.log_this(f"File path provided is not a string",1,self.get_pair())
|
# self.broker.logger.log_this(f"File path provided is not a string",1,self.get_pair())
|
||||||
|
|
@ -181,8 +192,15 @@ class StatusHandler:
|
||||||
self.status_dictionary["so_order_id"] = order_id
|
self.status_dictionary["so_order_id"] = order_id
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def set_safety_order(self, order):
|
def set_safety_orders(self, orders: list):
|
||||||
self.status_dictionary["safety_order"] = order
|
"""
|
||||||
|
Replaces the whole safety orders list
|
||||||
|
"""
|
||||||
|
self.status_dictionary["safety_orders"] = orders
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def set_safety_orders_filled(self, amount: int):
|
||||||
|
self.status_dictionary["safety_orders_filled"] = amount
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def set_next_so_price(self, price: float):
|
def set_next_so_price(self, price: float):
|
||||||
|
|
@ -381,35 +399,32 @@ class StatusHandler:
|
||||||
self.status_dictionary["deal_order_history"] = deal_history
|
self.status_dictionary["deal_order_history"] = deal_history
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def add_safety_order(self, order):
|
||||||
|
"""
|
||||||
|
Appends a newly-created safety order to the internal list
|
||||||
|
"""
|
||||||
|
self.status_dictionary["safety_orders"].append(order)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def remove_safety_order_by_id(self, order_id: str):
|
||||||
|
"""
|
||||||
|
Removes an order from the list (mostly used when that order is filled or canceled)
|
||||||
|
"""
|
||||||
|
orders = self.get_safety_orders()
|
||||||
|
self.status_dictionary["safety_orders"] = [order for order in orders if order["id"] != order_id]
|
||||||
|
return 0
|
||||||
|
|
||||||
def clear_deal_order_history(self):
|
def clear_deal_order_history(self):
|
||||||
self.status_dictionary["deal_order_history"] = []
|
self.status_dictionary["deal_order_history"] = []
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def update_deal_order_history(self, new_deal: dict):
|
def update_deal_order_history(self, new_deal: dict, note: str = ""):
|
||||||
# if not isinstance(new_deal, dict):
|
# if not isinstance(new_deal, dict):
|
||||||
# self.broker.logger.log_this(f"value provided is not a dict",1,self.get_pair())
|
# self.broker.logger.log_this(f"value provided is not a dict",1,self.get_pair())
|
||||||
self.status_dictionary["deal_order_history"].append(self.strip_order(new_deal))
|
id = new_deal["id"] if "id" in new_deal else None
|
||||||
|
self.status_dictionary["deal_order_history"].append(f"{note} - {id}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def strip_order(self, order):
|
|
||||||
try:
|
|
||||||
stripped_order = {"id": order["id"],
|
|
||||||
"symbol": order["symbol"],
|
|
||||||
"type": order["type"],
|
|
||||||
"side": order["side"],
|
|
||||||
"price": float(order["price"]),
|
|
||||||
"amount": float(order["amount"]),
|
|
||||||
"filled": float(order["filled"]),
|
|
||||||
"cost": float(order["cost"]),
|
|
||||||
"remaining": float(order["remaining"]),
|
|
||||||
"timestamp": order["timestamp"],
|
|
||||||
"fees": order["fees"]}
|
|
||||||
return stripped_order
|
|
||||||
except Exception as e:
|
|
||||||
self.broker.logger.log_this(f"Error stripping order: {e}",2)
|
|
||||||
return order
|
|
||||||
|
|
||||||
|
|
||||||
def save_to_file(self, file_path = None, is_backup = False):
|
def save_to_file(self, file_path = None, is_backup = False):
|
||||||
if file_path is None:
|
if file_path is None:
|
||||||
file_path = self.status_file_path
|
file_path = self.status_file_path
|
||||||
|
|
|
||||||
27
todo.txt
27
todo.txt
|
|
@ -1,32 +1,25 @@
|
||||||
Mandatory:
|
Mandatory:
|
||||||
=========
|
=========
|
||||||
1. Stats webpage.
|
0. Stats webpage.
|
||||||
2. Maintain local orderbooks for each trading pair, which enables:
|
1. Maintain local orderbooks for each trading pair, which enables:
|
||||||
2a. Smart order pricing: Prioritization of fill speed over instant profit or vice versa
|
2a. Smart order pricing: Prioritization of fill speed over instant profit or vice versa
|
||||||
3. Proper handling of order price too high/low in OKX (rare, it happens when under heavy volatility).
|
2. Proper handling of order price too high/low in OKX (rare, it happens when under heavy volatility).
|
||||||
4. Multiple safety orders open at the same time (to catch big volatility spikes more effectively)
|
3. API documentation.
|
||||||
5. Things that should be objects (it's not 1994):
|
4. Implement api key hashing.
|
||||||
* Orders.
|
5. Dockerize.
|
||||||
* Config (Mostly done).
|
6. Earn should be integrated into the instance, in order to be able to invest the idle funds from the short traders.
|
||||||
* Status (Mostly done).
|
|
||||||
6. API documentation.
|
|
||||||
7. Implement api key hashing.
|
|
||||||
8. Dockerize.
|
|
||||||
9. Cache generated status strings, only recalculate when prices change.
|
|
||||||
10. Inspect orderbook liquidity prior to changing mode from short to long (big sell market order needs to have liquidity).
|
|
||||||
|
|
||||||
|
|
||||||
Would be nice to have:
|
Would be nice to have:
|
||||||
=====================
|
=====================
|
||||||
0. Trader order: alphabetical; by uptime; by safety orders, by percentage_to_completion. (Although this may be more suitable for the web and mobile apps)
|
0. Trader order: alphabetical; by uptime; by safety orders, by percentage_to_completion. (Although this may be more suitable for the web and mobile apps)
|
||||||
1. Local implementation of amount_to_precision, cost_to_precision and price_to_precision. (Unless the plan is to continue to use CCXT forever)
|
1. Local implementation of amount_to_precision, cost_to_precision and price_to_precision. (Unless the plan is to continue to use CCXT forever)
|
||||||
2. Instead of cancelling and resending the take profit order, you could just edit it (Kucoin only supports editing on high frequency orders)
|
2. Instead of cancelling and resending the take profit order, edit it (Kucoin only supports editing on high frequency orders)
|
||||||
3. Round-robin trading pairs: Instead of a fixed list of trading pairs, after n closed deals the trader is terminated and a new one spawns, picking the trading pair
|
3. When autoswitching to long, instead of using a big market order, the last safety order should be a sell order of all the available funds.
|
||||||
|
4. Round-robin trading pairs: Instead of a fixed list of trading pairs, after n closed deals the trader is terminated and a new one spawns, picking the trading pair
|
||||||
from a pre-populated list (the trading pairs can be selected by using Yang-Zhang, Parkinson or another volatility indicator)
|
from a pre-populated list (the trading pairs can be selected by using Yang-Zhang, Parkinson or another volatility indicator)
|
||||||
This could be very benefitial, since it limits the long time commitment to a small list of trading pairs, enabling the instance to react to market trends very
|
This could be very benefitial, since it limits the long time commitment to a small list of trading pairs, enabling the instance to react to market trends very
|
||||||
rapidly.
|
rapidly.
|
||||||
4. Earn should also use funds from short traders.
|
|
||||||
4b. Should Earn be integrated to the instance?
|
|
||||||
|
|
||||||
|
|
||||||
Maybe it's a good idea?:
|
Maybe it's a good idea?:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,12 @@ try:
|
||||||
api_key = credentials.get_credentials("testnet_api_key")["key"]
|
api_key = credentials.get_credentials("testnet_api_key")["key"]
|
||||||
base_url = credentials.get_url("testnet") #type: ignore
|
base_url = credentials.get_url("testnet") #type: ignore
|
||||||
exchanges = {"Binance":"/binance"}
|
exchanges = {"Binance":"/binance"}
|
||||||
|
elif sys.argv[1]=="--local_testnet":
|
||||||
|
is_testnet = True
|
||||||
|
string_to_add = "LOCAL TESTNET "
|
||||||
|
api_key = credentials.get_credentials("local_testnet_api_key")["key"]
|
||||||
|
base_url = credentials.get_url("local_testnet") #type: ignore
|
||||||
|
exchanges = {"Binance":":5001"}
|
||||||
elif sys.argv[1]=="--mainnet":
|
elif sys.argv[1]=="--mainnet":
|
||||||
is_testnet = False
|
is_testnet = False
|
||||||
string_to_add = "MAINNET "
|
string_to_add = "MAINNET "
|
||||||
|
|
@ -37,7 +43,8 @@ INSTANCE
|
||||||
10) edit_call_wait_time 11) reload_markets 12) fetch_full_log
|
10) edit_call_wait_time 11) reload_markets 12) fetch_full_log
|
||||||
13) paused_traders 14) fetch_log 15) edit_cooldown_multiplier
|
13) paused_traders 14) fetch_log 15) edit_cooldown_multiplier
|
||||||
16) get_balance 17) cancel_global_last_call
|
16) get_balance 17) cancel_global_last_call
|
||||||
18) mod_default_order_size
|
18) mod_default_order_size 19) toggle_log_orders
|
||||||
|
20) refresh_log_cache
|
||||||
|
|
||||||
EARN
|
EARN
|
||||||
31) toggle_pause 32) get_step_size 33) set_step_size
|
31) toggle_pause 32) get_step_size 33) set_step_size
|
||||||
|
|
@ -56,9 +63,10 @@ TRADERS
|
||||||
62) mod_tp_level 63) last_call 64) deferred_last_call
|
62) mod_tp_level 63) last_call 64) deferred_last_call
|
||||||
65) toggle_pause 66) toggle_cleanup 67) toggle_autoswitch
|
65) toggle_pause 66) toggle_cleanup 67) toggle_autoswitch
|
||||||
68) toggle_check_old_long_price 69) switch_quote_currency
|
68) toggle_check_old_long_price 69) switch_quote_currency
|
||||||
70) reload_safety_order 71) view_old_long 72) switch_price
|
70) view_old_long 71) switch_price 72) reload_trader_config
|
||||||
73) reload_trader_config 74) toggle_liquidate_after_switch
|
73) toggle_liquidate_after_switch 74) base_add_calculation
|
||||||
75) base_add_calculation
|
75) mod_concurrent_safety_orders 76) force_trader_close
|
||||||
|
77) mod_order_size
|
||||||
|
|
||||||
98) Change broker 99) Exit
|
98) Change broker 99) Exit
|
||||||
'''
|
'''
|
||||||
|
|
@ -334,6 +342,19 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
elif command==19:
|
||||||
|
print("toggle_log_orders turns on or off the logging of orders")
|
||||||
|
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||||
|
url = f"{base_url}{port}/toggle_log_orders"
|
||||||
|
print(json.loads(requests.post(url, headers=headers).content))
|
||||||
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
elif command==20:
|
||||||
|
print("refresh_log_cache refreshes the log cache")
|
||||||
|
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||||
|
url = f"{base_url}{port}/refresh_log_cache"
|
||||||
|
print(json.loads(requests.post(url, headers=headers).content))
|
||||||
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
######################
|
######################
|
||||||
######## EARN ########
|
######## EARN ########
|
||||||
|
|
@ -561,10 +582,6 @@ if __name__=="__main__":
|
||||||
print("In order for the importing to be successful, a status file must exist in the status directory ")
|
print("In order for the importing to be successful, a status file must exist in the status directory ")
|
||||||
print("and the take profit order must be open.")
|
print("and the take profit order must be open.")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
tp_id_input = input("Input take profit order id to use (if any)")
|
|
||||||
forced_tp_id = None if tp_id_input=="" else tp_id_input
|
|
||||||
so_id_input = input("Input safety order id to use (if any)")
|
|
||||||
forced_so_id = None if so_id_input=="" else so_id_input
|
|
||||||
|
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
print("The input is invalid")
|
print("The input is invalid")
|
||||||
|
|
@ -573,9 +590,7 @@ if __name__=="__main__":
|
||||||
url = f"{base_url}{port}/import_pair"
|
url = f"{base_url}{port}/import_pair"
|
||||||
base,quote = trading_pair.split("/")
|
base,quote = trading_pair.split("/")
|
||||||
parameters = {"base": base,
|
parameters = {"base": base,
|
||||||
"quote": quote,
|
"quote": quote}
|
||||||
"forced_tp_id": forced_tp_id,
|
|
||||||
"forced_so_id": forced_so_id}
|
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
|
@ -796,22 +811,8 @@ if __name__=="__main__":
|
||||||
"new_quote": new_quote}
|
"new_quote": new_quote}
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==70:
|
elif command==70:
|
||||||
print("reload_safety_order reloads the safety order to the reader using the order id present in the status dictionary")
|
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
|
||||||
if not validate_pair(trading_pair):
|
|
||||||
print("The input is invalid")
|
|
||||||
break
|
|
||||||
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
|
||||||
url = f"{base_url}{port}/reload_safety_order"
|
|
||||||
base,quote = trading_pair.split("/")
|
|
||||||
parameters = {"base": base,
|
|
||||||
"quote": quote}
|
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
|
||||||
input("Press ENTER to continue ")
|
|
||||||
|
|
||||||
elif command==71:
|
|
||||||
print("Views the old_long information")
|
print("Views the old_long information")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -824,7 +825,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.get(url,headers=headers).content))
|
print(json.loads(requests.get(url,headers=headers).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==72:
|
elif command==71:
|
||||||
print("Returns the price target to reach to switch to long mode")
|
print("Returns the price target to reach to switch to long mode")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -836,7 +837,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.get(url,headers=headers).content))
|
print(json.loads(requests.get(url,headers=headers).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==73:
|
elif command==72:
|
||||||
print("Reloads from disk the configuration file of a trader")
|
print("Reloads from disk the configuration file of a trader")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -850,7 +851,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==74:
|
elif command==73:
|
||||||
print("toggle_liquidate_after_switch enables or disables the liquidation after an automatic switch to long of a short trader")
|
print("toggle_liquidate_after_switch enables or disables the liquidation after an automatic switch to long of a short trader")
|
||||||
print("This is only valid in a short trader, of course.")
|
print("This is only valid in a short trader, of course.")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
|
|
@ -865,7 +866,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==75:
|
elif command==74:
|
||||||
print("Returns the amount of safety orders that can be added to a short trader with the available funds")
|
print("Returns the amount of safety orders that can be added to a short trader with the available funds")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -875,4 +876,57 @@ if __name__=="__main__":
|
||||||
base,quote = trading_pair.split("/")
|
base,quote = trading_pair.split("/")
|
||||||
url = f"{base_url}{port}/base_add_so_calculation?base={base}"e={quote}"
|
url = f"{base_url}{port}/base_add_so_calculation?base={base}"e={quote}"
|
||||||
print(json.loads(requests.get(url,headers=headers).content))
|
print(json.loads(requests.get(url,headers=headers).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
elif command==75:
|
||||||
|
print("mod_concurrent_safety_orders modifies the amount of safety orders opened at the same time")
|
||||||
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
|
new_amount = input("Desired amount of orders: ")
|
||||||
|
if not validate_pair(trading_pair):
|
||||||
|
print("The input is invalid")
|
||||||
|
break
|
||||||
|
if not validate_int(new_amount):
|
||||||
|
print("The amount entered is invalid")
|
||||||
|
break
|
||||||
|
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||||
|
url = f"{base_url}{port}/mod_concurrent_safety_orders"
|
||||||
|
base,quote = trading_pair.split("/")
|
||||||
|
parameters = {"base": base,
|
||||||
|
"quote": quote,
|
||||||
|
"amount": new_amount}
|
||||||
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
elif command==76:
|
||||||
|
print("force_trader_close forces a trader to close the current position")
|
||||||
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
|
if not validate_pair(trading_pair):
|
||||||
|
print("The input is invalid")
|
||||||
|
break
|
||||||
|
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||||
|
url = f"{base_url}{port}/force_trader_close"
|
||||||
|
base,quote = trading_pair.split("/")
|
||||||
|
parameters = {"base": base,
|
||||||
|
"quote": quote}
|
||||||
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
elif command==77:
|
||||||
|
print("mod_order_size modifies the initial order size of a trader")
|
||||||
|
print("The change impacts as soon as the trader starts a new deal")
|
||||||
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
|
amount = input("Desired order size: ")
|
||||||
|
if not validate_pair(trading_pair):
|
||||||
|
print("The input is invalid")
|
||||||
|
break
|
||||||
|
if not validate_float_or_int(amount):
|
||||||
|
print("The amount entered is invalid")
|
||||||
|
break
|
||||||
|
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||||
|
url = f"{base_url}{port}/mod_order_size"
|
||||||
|
base,quote = trading_pair.split("/")
|
||||||
|
parameters = {"base": base,
|
||||||
|
"quote": quote,
|
||||||
|
"amount": amount}
|
||||||
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
|
input("Press ENTER to continue ")
|
||||||
|
|
@ -5,10 +5,8 @@ import calendar
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
from collections import deque
|
|
||||||
from typing import Iterable, List, Tuple
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from flask import Flask, jsonify, request, Response
|
from flask import Flask, jsonify, request
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue