DCAv2/main.py

2102 lines
73 KiB
Python

import datetime
import json
import os
import sys
import time
from threading import Thread
import sqlite3
import ccxt
from flask import Flask, jsonify, request
import exchange_wrapper
import trader
'''
In case the permissions of the certificate changes, reset them this way:
/ sudo su
# chmod -R 755 /etc/letsencrypt/live/
# chmod -R 755 /etc/letsencrypt/archive/
# ll /etc/letsencrypt/
'''
version = "2024.10.28"
'''
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
'''
yellow = "\033[0;33;40m"
green = "\033[0;32;40m"
red = "\033[0;31;40m"
blue = "\033[0;34;40m"
cyan = "\033[0;36;40m"
bright_white = "\033[0;97;40m"
bright_green = "\033[0;92;40m"
white = "\033[0;37;40m"
def seconds_to_time(total_seconds: float) -> str:
'''
Takes an int or float as an input and it returns a D:HH:MM:SS formatted string.
Parameters:
total_seconds (float): The number of seconds to convert
Returns:
str: The formatted string
'''
time_delta = datetime.timedelta(seconds=total_seconds)
hours = time_delta.seconds//3600
remainder = time_delta.seconds%3600
minutes = remainder//60
seconds = remainder%60
return f"{time_delta.days}:{hours:02d}:{minutes:02d}:{seconds:02d}"
def time_to_unix(year: str, month: str, day: str) -> int:
'''
Takes three integer values as an input and returns the unix time corresponding to that input
Parameters:
year (str): The year to convert
month (str): The month to convert
day (str): The day to convert
Returns:
int: The unix time corresponding to the input
'''
try:
return int(time.mktime(datetime.date(int(year), int(month), int(day)).timetuple()))
except Exception as e:
broker.logger.log_this(f"{e}")
return 0
def import_instance(pair: str) -> int:
'''
Imports an previously running trader instance from the status file.
Parameters:
pair (str): The trading pair to import with the format BASEQUOTE (no slash).
Returns:
int: 0 if successful
'''
broker.logger.log_this(f"Importing {pair}")
#with open(f"status/{pair}.status", "r") as f:
# status_file_contents = json.load(f)
with open(f"configs/{pair}.json", "r") as g:
config_file_contents = json.load(g)
instances_to_add.append(trader.trader(broker,config_file_contents,is_import=True))
if pair not in tickers:
tickers.append(pair)
return 0
def add_instance(base: str, quote: str) -> int:
'''
Adds a new instance of the trader class to the running_instances list.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
Returns:
int: 0 if the instance was successfully added, 1 if the pair is already running.
'''
#Check if the pair is already running
pair = f"{base}{quote}"
for x in running_instances:
if f"{x.base}{x.quote}"==pair:
broker.logger.log_this(f"Pair already running, duplicate instances are not allowed",1,pair)
return 1
#Check if config file already exists; if not, generate a new one
if not os.path.isfile(f"configs/{pair}.json"):
broker.logger.log_this(f"Config file does not exist. Generating...",1,pair)
details = generate_config_file(base,quote)
with open(f"configs/{pair}.json","w") as cf:
cf.write(json.dumps(details, indent=4))
else:
with open(f"configs/{pair}.json", "r") as cf:
details = json.load(cf)
#Initialize the trader object and add the pair to the tickers list
instances_to_add.append(trader.trader(broker,details))
if pair not in tickers:
tickers.append(pair)
return 0
def initialize_instance(pair: str) -> int:
'''
Loads the pair config file and initializes the trader object by adding it to the running instances list
Parameters:
pair (str): The pair to initialize with the format BASEQUOTE (no slash)
Returns:
int: 0 if successful
'''
with open(f"configs/{pair}.json", "r") as y:
config_details = json.load(y)
broker.logger.log_this(f"Initializing {pair}")
running_instances.append(trader.trader(broker,config_details))
if pair not in tickers:
tickers.append(pair)
return 0
def generate_config_file(base: str, quote: str) -> dict:
'''
Generates a config file with default values for a given pair and returns that content in dictionary form.
TODO: Add a pair check against exchange's tickers data to properly support BASEQUOTE input format (without a slash)
1. load tickers
2. search for pair in dictionary
3. assign proper base and quote values from the dictionary
'''
return {"pair": f"{base}/{quote}",
"order_size": broker.get_default_order_size(),
"tp_level": 1.02,
"no_of_safety_orders": 30,
"safety_order_deviance": 2,
"safety_order_scale": 0.0105,
"write_logs": True,
"cleanup": True,
"telegram": True,
"tp_mode": 3,
"tp_table": [],
"is_short": False,
"autoswitch": False,
"check_old_long_price": True,
"attempt_restart": True,
"dynamic_so_deviance": True,
"dsd_range": 1,
"slippage_default_threshold": .02
}
def set_exchange(config: dict):
'''
Takes the config dictionary as an input and returns the exchange object
Parameters
config (dict): The config dictionary
Returns
exchange (ccxt.Exchange): The exchange object
'''
timeout = 10000
if config["exchange"]=="binance":
exchange_class = getattr(ccxt, "binance")
exchange = exchange_class({
"apiKey": config["key"],
"secret": config["secret"],
"timeout": timeout,
"enableRateLimit": True
})
exchange.options["warnOnFetchOpenOrdersWithoutSymbol"] = False
if config["is_sandbox"]:
exchange.set_sandbox_mode(True)
elif config["exchange"]=="kucoin":
exchange_class = getattr(ccxt, "kucoin")
exchange = exchange_class({
"apiKey": config["key"],
"secret": config["secret"],
"password": config["password"],
"timeout": timeout,
"enableRateLimit": True
})
elif config["exchange"]=="okex":
exchange_class = getattr(ccxt, "okx")
exchange = exchange_class({
"apiKey": config["key"],
"secret": config["secret"],
"password": config["password"],
"timeout": timeout,
"enableRateLimit": True
})
elif config["exchange"]=="gateio":
exchange_class = getattr(ccxt, "gateio")
exchange = exchange_class({
"apiKey": config["key"],
"secret": config["secret"],
"timeout": timeout,
"enableRateLimit": True
})
elif config["exchange"]=="hitbtc":
exchange_class = getattr(ccxt, "hitbtc")
exchange = exchange_class({
"apiKey": config["key"],
"secret": config["secret"],
"timeout": timeout,
"enableRateLimit": True
})
elif config["exchange"]=="poloniex":
exchange_class = getattr(ccxt, "poloniex")
exchange = exchange_class({
"apiKey": config["key"],
"secret": config["secret"],
"timeout": timeout,
"enableRateLimit": True
})
else:
print(f"{time.strftime('[%Y/%m/%d %H:%M:%S]')} | Exchange not known or misspelled")
return None
exchange.options['createMarketBuyOrderRequiresPrice'] = False
return exchange
def restart_pair_no_json(base: str, quote: str) -> int:
'''
Trader restart routine
Parameters:
base (str): Base currency
quote (str): Quote currency
Returns:
int: 0 if successful, 1 if not
'''
try:
order_list = broker.fetch_full_orders(tickers)
for x in running_instances:
if f"{base}/{quote}"==x.pair:
x.pause = True
#Backing up old status file
x.write_status_file(True)
#Here, we could open a duster (if needed)
for order in order_list:
if order["symbol"]==f"{base}/{quote}" and x.is_short and order["side"]=="sell":
broker.logger.log_this(f"Cancelling old sell orders",2,f"{base}/{quote}")
broker.cancel_order(order["id"],order["symbol"])
elif order["symbol"]==f"{base}/{quote}" and not x.is_short and order["side"]=="buy":
broker.logger.log_this(f"Cancelling old buy orders",2,f"{base}/{quote}")
broker.cancel_order(order["id"],order["symbol"])
running_instances.remove(x)
add_instance(base,quote)
return 0
return 1
except Exception as e:
broker.logger.log_this(f"Exception in restart_pair_no_json: {e}",1,f"{base}/{quote}")
return 1
def main_loop():
global last_market_reload
global reload_interval
global screen_buffer
#global paused_pairs
while True:
#Restart traders that have the restart flag raised and remove traders that have the quit flag raised
for x in running_instances:
if x.restart and x.config_dict["attempt_restart"]:
broker.logger.log_this(f"Restarting trader",1,x.pair)
restart_pair_no_json(x.base,x.quote)
if x.quit:
#Here, check if a duster is needed
broker.logger.log_this(f"Quit flag raised, removing pair.",0,x.pair)
if f"{x.base}{x.quote}" in tickers:
tickers.remove(f"{x.base}{x.quote}")
broker.remove_pair_from_config(f"{x.base}{x.quote}")
broker.rewrite_config_file()
if x.pair in worker_status:
del(worker_status[x.pair])
running_instances.remove(x)
#Adds pending traders
if bool(instances_to_add):
for x in instances_to_add:
running_instances.append(x)
instances_to_add.clear()
#Prepares the trader threads
open_orders = broker.fetch_open_orders(tickers)
pairs_to_fetch = []
online_pairs = []
for x in running_instances:
threads.append(Thread(target=x.check_status,args=(open_orders,)))
online_pairs.append(f"{x.base}{x.quote}")
pairs_to_fetch.append(x.pair)
#Here, append the dusters' pairs to pairs_to_fetch, if missing.
#
#
#
#
#
#Start the trader threads
for t in threads:
try:
t.start()
except Exception as e:
broker.logger.log_this(f"Error starting thread - {e}")
#Wait for the trader threads to complete
for t in threads:
try:
t.join()
except Exception as e:
broker.logger.log_this(f"Error joining thread: {e}")
threads.clear()
#Fetch prices
price_list = broker.get_prices(pairs_to_fetch)
#Here, assign the prices to the dusters (if any)
curr = 0
top = 0
for x in running_instances:
if not x.is_short:
curr += int(x.status_dict["so_amount"]) # For the safety order occupancy percentage calculation
top += int(x.config_dict["no_of_safety_orders"]) # It shows the percentage of safety orders not filled
if not x.quit: #Why? Maybe to protect return_status() from weird errors if the trader errored out?
try:
if x.pair in price_list and price_list[x.pair] is not None:
x.status_dict["price"] = price_list[x.pair]
except Exception as e:
broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,x.pair)
#if "status_string" in x.status_dict:
# screen_buffer.append(x.status_dict["status_string"])
worker_status[x.pair] = x.status_dict
#Clear the screen buffer
screen_buffer.clear()
#Append worker data to screen buffer, shorts first.
for x in running_instances:
if x.is_short and "status_string" in x.status_dict:
screen_buffer.append(x.status_dict["status_string"])
for x in running_instances:
if not x.is_short and "status_string" in x.status_dict:
screen_buffer.append(x.status_dict["status_string"])
#Updates some global status variables prior to deletion of those
global_status["online_workers"] = online_pairs.copy()
#Check for paused pairs
global_status["paused_traders"] = [x.pair for x in running_instances if x.pause]
if global_status["paused_traders"]:
screen_buffer.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}")
#Check for paused pairs
for x in running_instances:
if x.pause:
screen_buffer.append(f"{x.pair} paused: {x.status_dict['pause_reason']}")
#Prints general info
instance_uptime = int(time.time()) - instance_start_time
#long_traders = len([None for x in running_instances if not x.is_short])
#short_traders = len([None for x in running_instances if x.is_short])
screen_buffer.append(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {len(running_instances)} traders online | Instance uptime: {seconds_to_time(instance_uptime)}"))
if top==0: #To protect from division by zero when there are no traders active
so_index=100
else:
so_index = round(curr/top*100,2)
color = red
if so_index<70:
color = yellow
if so_index<35:
color = green
is_testnet = "TESTNET " if broker.get_config()["is_sandbox"] else ""
screen_buffer.append(f"{bright_white}{broker.get_config()['exchange'].upper()} {is_testnet}{white}| DCAv2 {version} | CCXT v{ccxt.__version__} | Safety order occupancy: {color}{so_index}%{white}")
screen_buffer.append(blue + "="*80 + white)
#Print screen buffer
for line in screen_buffer:
print(line)
#Updates global status and remove keys that should not be public
global_status["instance_uptime"] = instance_uptime
global_status["config"] = broker.get_config()
for item in ["bot_chatID", "bot_token", "key", "secret", "password"]:
if item in global_status["config"]:
del(global_status["config"][item])
#Toggle pauses
if toggle_pauses:
for instance in running_instances:
if instance.pair in toggle_pauses:
instance.pause = not instance.pause
toggle_pauses.clear()
#Checks if market reload is due
if time.time()-last_market_reload>reload_interval:
broker.reload_markets()
last_market_reload = time.time()
#Take a deep breath and start all over again
time.sleep(broker.get_lap_time())
def load_keys_from_db(file_name: str) -> list:
'''
Load valid API keys
Parameters
----------
file_name : str
Name of the database file
Returns
-------
valid_keys : list
List of valid API keys
'''
database_connection = sqlite3.connect(file_name)
database_cursor = database_connection.cursor()
database_cursor.execute("SELECT * FROM credentials_table")
data = database_cursor.fetchall()
database_connection.close()
valid_keys = [line[1] for line in data]
return valid_keys
def display_splashscreen():
'''
Display splash screen
'''
print("""
###### ##### # #####
# # # # # # # #
# # # # # #
# # # # # # # #####
# # # ####### # # #
# # # # # # # # #
###### ##### # # ## #######
""")
print(time.strftime(f"[%Y/%m/%d %H:%M:%S] | DCAv2 version {version}"))
print(time.strftime(f"[%Y/%m/%d %H:%M:%S] | Using CCXT version {ccxt.__version__}"))
return None
#########################
######### API ###########
#########################
base_api = Flask(__name__)
@base_api.route("/global_status", methods=['GET'])
def return_global_status():
'''
GET request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_global_status()
return jsonify({'Error': 'API key invalid'}), 401
#@base_api.route("/paused_traders", methods=['GET'])
#def return_paused_status():
# '''
# GET request
#
# Parameters:
# None
# '''
# if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
# return unwrapped_paused_traders()
# return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/worker_status", methods=['GET'])
def return_worker_status():
'''
GET request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
base = request.args.get("base")
quote = request.args.get("quote")
return unwrapped_return_worker_status(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/view_old_long", methods=["GET"])
def return_old_long():
'''
GET request
Parameters:
base: str
quote: str
from_file: bool
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
base = request.args.get("base")
quote = request.args.get("quote")
from_file = request.args.get("from_file")
return unwrapped_view_old_long(base,quote,from_file)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/get_all_worker_status", methods=['GET'])
def return_all_worker_status():
'''
GET request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_return_all_worker_status()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/add_pair", methods=['POST'])
def add_pair():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_add_pair(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/remove_pair", methods=['POST'])
def remove_pair():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_remove_pair(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/restart_pair", methods=['POST'])
def restart_pair():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_restart_pair(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/import_pair", methods=['POST'])
def import_pair():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_import_pair(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/switch_to_long", methods=['POST'])
def switch_to_long():
'''
POST request
Parameters:
base: str
quote: str
calculate_profits: int
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
calculate_profits = data["calculate_profits"]
return unwrapped_switch_to_long(base,quote,calculate_profits)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/switch_to_short", methods=['POST'])
def switch_to_short():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_switch_to_short(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/load_old_long", methods=['POST'])
def load_old_long():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_load_old_long(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/add_so", methods=['POST'])
def add_so():
'''
POST request
Parameters:
base: str
quote: str
amount: int
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
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_add_safety_orders(base,quote,amount)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/mod_tp_level", methods=['POST'])
def mod_tp_level():
'''
POST request
Parameters:
base: str
quote: str
amount: float
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
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_tp_level(base,quote,amount)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/mod_global_tp_level", methods=['POST'])
def mod_global_tp_level():
'''
POST request
Parameters:
amount: float
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
amount = data["amount"]
return unwrapped_mod_global_tp_level(amount)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/last_call", methods=['POST'])
def last_call():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_last_call(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/deferred_last_call", methods=['POST'])
def deferred_last_call():
'''
POST request
Parameters:
base: str
quote: str
yyyymmdd: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
yyyymmdd = data["yyyymmdd"]
return unwrapped_deferred_last_call(base,quote,yyyymmdd)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/toggle_pause", methods=['POST'])
def toggle_pause():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_toggle_pause(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/global_last_call", methods=['POST'])
def global_last_call():
'''
POST request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_global_last_call()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/add_quote", methods=['POST'])
def add_quote():
'''
POST request
Parameters:
base: str
quote: str
amount: float
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
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_add_quote(base,quote,amount)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/missing_pairs", methods=['GET'])
def missing_pairs():
'''
GET request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_missing_pairs()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/toggle_cleanup", methods=['POST'])
def toggle_cleanup():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_toggle_cleanup(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/toggle_autoswitch", methods=['POST']) #type:ignore
def toggle_autoswitch():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_toggle_autoswitch(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/toggle_check_old_long_price", methods=['POST'])#type:ignore
def toggle_check_old_long_price():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_toggle_check_old_long_price(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/switch_quote_currency", methods=['POST'])
def switch_quote_currency():
'''
POST request
Parameters:
base: str
quote: str
new_quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
new_quote = data["new_quote"]
return unwrapped_switch_quote_currency(base,quote,new_quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/toggle_restart", methods=['POST'])
def toggle_restart():
'''
POST request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_toggle_restart()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/toggle_telegram", methods=['POST'])
def toggle_telegram():
'''
POST request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_toggle_telegram()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/server_time", methods=['GET'])
def server_time():
'''
GET request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_server_time()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/trader_time", methods=['GET'])
def trader_time():
'''
GET request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_trader_time()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/edit_loop_wait_time", methods=['POST'])
def loop_wait_time():
'''
POST request
Parameters:
wait_time: float
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
wait_time = data["wait_time"]
return unwrapped_loop_wait_time(wait_time)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/edit_call_wait_time", methods=['POST'])
def call_wait_time():
'''
POST request
Parameters:
wait_time: float
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
wait_time = data["wait_time"]
return unwrapped_call_wait_time(wait_time)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/edit_cooldown_multiplier", methods=['POST'])
def edit_cooldown_multiplier():
'''
POST request
Parameters:
cooldown_multiplier: float
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
multiplier = data["cooldown_multiplier"]
return unwrapped_edit_cooldown_multiplier(multiplier)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/reload_markets", methods=['POST'])
def reload_markets():
'''
POST request
Parameters:
base: str
quote: str
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
return unwrapped_reload_markets()
return jsonify({'Error': 'API key invalid'}), 401
@base_api.route("/reload_safety_order", methods=['POST'])
def reload_safety_order():
'''
POST request
Parameters:
None
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
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'})
return jsonify({'Error': 'API key invalid'}), 401
def run_API():
base_api.run(host="0.0.0.0", port=broker.get_config()["port"])
###########################
# Unwrapped API functions #
###########################
def unwrapped_global_status():
'''
Returns the global status dictionary of the instance.
'''
return jsonify(global_status)
def unwrapped_return_worker_status(base,quote):
'''
Returns the individual status dictionary of the trader.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
Returns:
dict: The status dictionary of the trader.
'''
if f"{base}/{quote}" in worker_status:
return jsonify(worker_status[f"{base}/{quote}"])
return jsonify({"Error": "Worker does not exist"})
def unwrapped_return_all_worker_status():
'''
Returns the status dictionary of all traders.
Returns:
dict: The status dictionary of all traders.
'''
return jsonify(worker_status)
def unwrapped_add_pair(base,quote):
'''
Adds a trader to the instance.
Parameters:
base (str): The base currency of the trader.
quote (str): The quote currency of the trader.
Returns:
jsonified dictionary detailing the outcome of the operation.
'''
try:
#Check if the trader is already running
for x in running_instances:
if f"{base}/{quote}"==x.pair:
broker.logger.log_this(f"Pair already running",1,f"{base}/{quote}")
return jsonify({"Error": "Pair already running"})
#Check if the market exists and it's open
markets = broker.exchange.load_markets()
if f"{base}/{quote}" not in markets:
broker.logger.log_this(f"Market does not exist",1,f"{base}/{quote}")
return jsonify({"Error": "Market does not exist"})
if not markets[f"{base}/{quote}"]["active"]:
broker.logger.log_this(f"Market is inactive",1,f"{base}/{quote}")
return jsonify({"Error": "Market is inactive"})
broker.logger.log_this(f"Initializing trader",2,f"{base}/{quote}")
add_instance(base,quote)
broker.add_pair_to_config(f"{base}{quote}")
#Adding the pair to the config file (if it's not there yet)
#1. Read the config file
with open(sys.argv[1],"r") as f:
temp_details = json.load(f)
if base+quote not in temp_details["pairs"]:
#2. Save the current config file as .bak
with open(f"{sys.argv[1]}.bak","w") as c:
c.write(json.dumps(temp_details, indent=4))
#3. Add the pair to the right list
temp_details["pairs"].append(f"{base}{quote}")
#4. Write the config file
with open(sys.argv[1],"w") as c:
c.write(json.dumps(temp_details, indent=4))
broker.logger.log_this(f"Broker's config file updated",2,f"{base}/{quote}")
else:
broker.logger.log_this(f"Pair already included in the config file",2,f"{base}/{quote}")
return jsonify({"Success": "Pair added"})
except Exception as e:
broker.logger.log_this(f"Exception while initializing new instance: {e}",1,f"{base}/{quote}")
return jsonify({"Error": "Error initializing new instance."})
def unwrapped_remove_pair(base,quote):
'''
Removes a trader from and instance
Parameters:
base (str): The base currency of the pair
quote (str): The quote currency of the pair
Returns:
jsonified dictionary detailing the outcome of the operation.
'''
try:
for x in running_instances:
if f"{base}/{quote}"==x.pair:
x.quit = True
return jsonify({"Success": "Pair to be removed"})
except Exception as e:
broker.logger.log_this(f"Exception while removing instance: {e}",1,f"{base}/{quote}")
return jsonify({"Error": "Halp"})
def unwrapped_restart_pair(base,quote):
'''
Restarts a trader instance
Parameters:
base (str): The base currency of the pair
quote (str): The quote currency of the pair
Returns:
jsonified dictionary detailing the outcome of the operation.
'''
if restart_pair_no_json(base,quote)==0:
return jsonify({"Success": "Pair restarted"})
return jsonify({"Error": "Halp"})
def unwrapped_import_pair(base,quote):
'''
Imports a previously running pair
Parameters:
base (str): The base currency of the pair
quote (str): The quote currency of the pair
Returns:
jsonified dictionary detailing the outcome of the operation.
'''
try:
import_instance(base+quote)
broker.add_pair_to_config(f"{base}{quote}")
broker.rewrite_config_file()
broker.logger.log_this(f"Done",2,f"{base}/{quote}")
return jsonify({"Success": "Pair imported successfully"})
except Exception as e:
broker.logger.log_this(f"Exception while importing instance: {e}",1,f"{base}/{quote}")
return jsonify({"Error": "Error importing instance"})
def unwrapped_switch_to_long(base,quote,calculate_profits):
'''
Switches a pair to long mode.
Parameters:
base (str): The base currency of the pair
quote (str): The quote currency of the pair
calculate_profits (int): 1 if you want to calculate the profits, 0 if you don't
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
ignore_old_long = int(calculate_profits)!=1
#Close trader and orders and pull info our of the orders
if f"{base}{quote}" not in broker.get_pairs():
return jsonify({"Error": "Pair not running"})
for x in running_instances:
if f"{base}/{quote}"==x.pair:
x.pause = True
if x.switch_to_long(ignore_old_long=ignore_old_long)==1:
return jsonify({"Error": "Error in switch_to_long()"})
if x.start_bot()==1:
x.quit = True
return jsonify({"Error": "Error switching to long mode (wAPI)"})
return jsonify({"Success": "Pair switched to long mode"})
return jsonify({"Error": "Pair not found"})
def unwrapped_switch_to_short(base,quote):
'''
Switches a pair to short mode.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
Returns
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
#Close trader and orders and pull info our of the orders
if f"{base}{quote}" not in broker.get_pairs():
return jsonify({"Error": "Pair not running"})
for x in running_instances:
if f"{base}/{quote}"==x.pair and x.switch_to_short()==1:
return jsonify({"Error": "Error in switch_to_short()"})
#Restart instance
try:
broker.logger.log_this(f"Reinitializing trader",2,f"{base}/{quote}")
for x in running_instances:
if f"{base}/{quote}"==x.pair:
#Clearing some variables
#x.fees_paid_in_quote = 0
#x.fees_paid_in_base = 0
x.tp_order = x.broker.empty_order
x.so = x.broker.empty_order
#x.safety_price_table.clear() #This clearing (probably) is the origin of the update_status error after switching to short.
#x.safety_order_index = 0
#Reloading config file
x.config_dict = x.reload_config_dict()
#Enabling autoswitch
x.config_dict["autoswitch"] = True
if x.start_bot()==1:
x.quit = True
return jsonify({"Error": "Error switching to short mode (wAPI)"})
return jsonify({"Success": "Pair switched to short mode"})
except Exception as e:
broker.logger.log_this(f"Exception while reinitializing instance: {e}",1,f"{base}/{quote}")
return jsonify({"Error": "Can't initialize trader"})
return jsonify({"Error": "Halp"})
def unwrapped_load_old_long(base,quote):
'''
Loads an old_long file to the status dictionary of a trader
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
Returns
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
#Load the file
try:
with open(f"{base}{quote}.oldlong") as ol:
old_long = json.load(ol)
except Exception as e:
broker.logger.log_this(f"Exception while loading old_long file: {e}",1,f"{base}/{quote}")
return jsonify({"Error": "old_long file of that pair does not exist."})
#Check that the file has the proper keys
if not any(["tp_price" in old_long, "tp_amount" in old_long, "quote_spent" in old_long, "datetime" in old_long]):
broker.logger.log_this(f"old_long file invalid: keys missing.",1,f"{base}/{quote}")
return jsonify({"Error": "File is invalid or missing keys"})
#Maybe here we could also check that the keys have the proper values
#Creates (or modifies) a key in the status dictionary and assigns the contents of the file to that same key.
for x in running_instances:
if x.pair==f"{base}/{quote}":
x.status_dict["old_long"]=old_long
x.update_status(True)
return jsonify({"Success": "old_long file loaded to status_dict"})
return jsonify({"Error": "Pair not found"})
def unwrapped_view_old_long(base,quote,from_file):
'''
Returns the content of an old_long file
Parameters:
base (str): The base currency of the pair
quote (str): The quote currency of the pair
from_file (int): 1 if the file is to be loaded from the file system, 0 if it is to be loaded from the trader's status dictionary.
Returns:
jsonify: A jsonified dictionary containing the old_long info.
'''
try:
if int(from_file)==1:
with open(f"{base}{quote}.oldlong") as ol:
old_long = json.load(ol)
return jsonify(old_long)
for x in running_instances:
if f"{base}/{quote}"==x.pair:
return jsonify(x.status_dict["old_long"])
return jsonify({"Error": "Pair not found"})
except Exception as e:
broker.logger.log_this(f"Exception while viewing old_long info: {e}",1,f"{base}/{quote}")
return jsonify({"Error": f"{e}"})
def unwrapped_add_safety_orders(base,quote,amount):
'''
Increases the amount of safety orders that a trader can use. Once the current deal is closed, the value returns to the one in the config
file.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
amount (str): The amount of safety orders to add.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
try:
for x in running_instances:
if f"{base}/{quote}"==x.pair:
x.pause = True
#x.no_of_safety_orders += int(amount)
x.config_dict["no_of_safety_orders"]+=int(amount)
broker.logger.log_this("Recalculating safety price table...",1,f"{base}/{quote}")
x.safety_price_table = x.calculate_safety_prices(x.start_price,x.config_dict["no_of_safety_orders"],x.config_dict["safety_order_deviance"])
broker.logger.log_this(f"Done. Added {amount} safety orders",1,f"{base}/{quote}")
x.update_status(True)
x.pause = False
return jsonify({"Success": f"Done. Added {amount} safety orders"})
return jsonify({"Error": "Pair not found"})
except Exception as e:
broker.logger.log_this(f"{e}",2,f"{base}/{quote}")
return jsonify({"Error": "Error adding safety orders"})
def unwrapped_mod_tp_level(base,quote,amount):
'''
Modifies the take profit percentage of a pair. It applies the new percentage only after a new TP order is sent.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
amount (str): The new take profit percentage.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
try:
for x in running_instances:
if f"{base}/{quote}"==x.pair:
x.config_dict["tp_level"]=float(amount)
broker.logger.log_this("Done. The change will take effect when the next take profit order is placed",2,f"{base}/{quote}")
return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"})
except Exception:
broker.logger.log_this("Error changing percentage. Ignoring...",2,f"{base}/{quote}")
return jsonify({"Error": "Error changing percentage"})
def unwrapped_mod_global_tp_level(amount):
'''
Modifies the take profit percentage of all pairs. It applies the new percentage only after a new TP order is sent.
Parameters:
amount (float): The new percentage to be used.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
for x in running_instances:
try:
x.config_dict["tp_level"]=float(amount)
broker.logger.log_this("Done. The change will take effect when the next take profit order is placed",2)
except Exception:
broker.logger.log_this("Error changing percentage. Ignoring.",2)
return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"})
def unwrapped_last_call(base,quote):
'''
Signals the trader to stop opening new deals once the current one is closed.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
try:
if f"{base}{quote}" in broker.get_pairs():
#read_config["pairs"].remove(base+quote)
for x in running_instances:
if f"{base}/{quote}"==x.pair:
x.stop_when_profit = not x.stop_when_profit
x.update_status(True)
if x.stop_when_profit:
return jsonify({"Success": "Trader scheduled to go offline when profit is reached"})
return jsonify({"Success": "Last call cancelled"})
return jsonify({"Error": "Trader does not exist"})
except Exception:
return jsonify({"Error": "Halp"})
def unwrapped_deferred_last_call(base,quote,yyyymmdd):
'''
Programs the trader to not open new deals from a certain future date. Like a VCR.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
yyyymmdd (str): The date in YYYYMMDD format.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
year = yyyymmdd[:4]
month = yyyymmdd[4:6]
day = yyyymmdd[6:8]
limit = time_to_unix(year,month,day)
if limit==0:
return jsonify({"Error": "Can't convert date to unix"})
for x in running_instances:
if f"{base}{quote}"==x.pair:
x.config_dict["stop_time"] = limit
#save config file to disk
x.broker.rewrite_config_file()
return jsonify({"Success": f"Trader scheduled to go offline when profit is reached after {limit}"})
except Exception:
return jsonify({"Error": "Halp"})
def unwrapped_toggle_pause(base,quote):
'''
Toggles the pause flag of a trader.
When a trader is paused, no new safety orders are sent to the exchange and the take profit order is unmonitored.
Although it could be useful to close the trader if the tp order is filled anyway.
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:
toggle_pauses.append(f"{base}/{quote}")
for instance in running_instances:
if instance.pair==f"{base}/{quote}":
if instance.pause:
return jsonify({"Success": "Trader will be resumed"})
return jsonify({"Success": "Trader will be paused"})
return jsonify({"Error": "Trader does not exist"})
except Exception:
return jsonify({"Error": "Halp"})
def unwrapped_global_last_call():
'''
Signals all traders to stop opening new trades when the current ones closes.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
if broker.get_pairs!=[]:
broker.clear_pairs()
for x in running_instances:
x.stop_when_profit = True
broker.logger.log_this("Modified flag",2,f"{x.base}/{x.quote}")
return jsonify({"Success": "All traders scheduled to go offline when profit is reached"})
else:
for x in running_instances:
x.stop_when_profit = False
broker.logger.log_this("Modified flag",2,f"{x.base}/{x.quote}")
broker.add_pair_to_config(f"{x.base}{x.quote}")
return jsonify({"Success": "Last call canceled"})
except Exception:
return jsonify({"Error": "Halp"})
def unwrapped_add_quote(base,quote,amount):
'''
Adds more funds to the deal, bringing down the average buy price.
I do not recommend to use this, it's preferable to switch to short mode, but you do you.
Maybe it's more useful in a bull market's high volatility moment.
Parameters:
base (str): The base currency of the pair
quote (str): The quote currency of the pair
amount (float): The amount of quote currency to add
Returns:
json: A jsonified dictionary detailing the outcome of the operation.
'''
for x in running_instances:
if f"{base}/{quote}"==x.pair:
if x.is_short:
return jsonify({"Error": "Quote can't be added to short bots"})
x.pause = True
new_average_price = (x.total_amount_of_quote+float(amount))/(x.total_amount_of_base+(float(amount)/x.status_dict["price"]))
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {x.quote}",2,f"{base}/{quote}")
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*x.get_tp_level()} {x.quote}",2,f"{base}/{quote}")
new_order = broker.new_market_order(x.pair,float(amount),"buy")
if new_order is None:
broker.logger.log_this("Error: Market order returned None",2,f"{base}/{quote}")
x.pause = False
return jsonify({"Error": "Market order returned None"})
while True:
time.sleep(broker.get_wait_time())
returned_order = broker.get_order(new_order["id"],x.pair)
if returned_order==broker.empty_order:
broker.logger.log_this("Problems sending the order",2,f"{base}/{quote}")
x.pause = False
return jsonify({"Error": "Problems sending the order"})
elif returned_order["status"]=="expired":
x.pause = False
return jsonify({"Error": "New order expired"})
elif returned_order["status"]=="closed":
broker.logger.log_this("Order sent",2,f"{base}/{quote}")
new_fees_in_base, new_fees_in_quote = x.parse_fees(returned_order)
x.fees_paid_in_base += new_fees_in_base
x.fees_paid_in_quote += new_fees_in_quote
x.total_amount_of_base = x.total_amount_of_base + returned_order["filled"] - new_fees_in_base
x.total_amount_of_quote += returned_order["cost"]
broker.logger.log_this("Cancelling old take profit order and sending a new one",2,f"{base}/{quote}")
attempts = 5
while broker.cancel_order(x.tp_order["id"],x.pair)==1:
broker.logger.log_this("Can't cancel old take profit order, retrying...",2,f"{base}/{quote}")
time.sleep(broker.get_wait_time())
attempts-=1
if attempts==0:
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,f"{base}/{quote}")
x.pause = False
return jsonify({"Error": "Can't cancel old take profit order."})
x.take_profit_price = x.total_amount_of_quote/x.total_amount_of_base*x.get_tp_level()
x.tp_order = broker.new_limit_order(x.pair,x.total_amount_of_base,"sell",x.take_profit_price)
x.update_status(True)
break
else:
broker.logger.log_this("Waiting for initial order to get filled",2,f"{base}/{quote}")
broker.logger.log_this(f"{returned_order}",2,f"{base}/{quote}")
time.sleep(broker.get_wait_time())
x.pause = False
broker.logger.log_this("Done",2,f"{base}/{quote}")
return jsonify({"Success": "Quote added successfully"})
return jsonify({"Error": "Something horrible happened :S"})
def unwrapped_missing_pairs():
'''
Returns a list of the pairs that are not running that are included in the config file
Returns:
jsonify: A jsonify object with a list of the missing pairs
'''
try:
missing_pairs = broker.get_pairs()
for trader in running_instances:
if f"{trader.base}{trader.quote}" in missing_pairs:
missing_pairs.remove(f"{trader.base}{trader.quote}")
return jsonify({"Success": missing_pairs})
except Exception as e:
broker.logger.log_this(f"Exception while querying for missing pairs: {e}",1)
return jsonify({"Error": "Error fetching running pairs"})
def unwrapped_toggle_cleanup(base,quote):
'''
Signals a trader to enable or disable the cleanup routine.
Parameters:
base (str): The base currency of the pair to toggle
quote (str): The quote currency of the pair to toggle
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
pair_to_toggle = f"{base}/{quote}"
for x in running_instances:
if pair_to_toggle==x.pair:
x.config_dict["cleanup"] = not x.config_dict["cleanup"]
if x.config_dict["cleanup"]:
return jsonify({"Success": "Cleanup turned ON"})
return jsonify({"Success": "Cleanup turned OFF"})
except Exception as e:
broker.logger.log_this(f"Exception while toggling cleanup: {e}",1,f"{base}{quote}")
return jsonify({"Error": "Halp"})
return jsonify({"Error": "Task failed successfully"})
def unwrapped_toggle_autoswitch(base,quote):
'''
Signals a trader to enable or disable autoswitch.
Parameters:
base (str): The base currency of the trading pair.
quote (str): The quote currency of the trading pair.
Returns:
A jsonified dictionary detailing the outcome of the operation.
'''
try:
pair_to_toggle = f"{base}/{quote}"
for x in running_instances:
if pair_to_toggle==x.pair:
if x.config_dict["autoswitch"]:
broker.logger.log_this("Autoswitch turned OFF",1,f"{base}/{quote}")
x.config_dict["autoswitch"] = False
return jsonify({"Success": "Autoswitch it's now OFF"})
else:
broker.logger.log_this("Autoswitch turned ON",1,f"{base}/{quote}")
x.config_dict["autoswitch"] = True
return jsonify({"Success": "Autoswitch it's now ON"})
except Exception as e:
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,f"{base}{quote}")
return jsonify({"Error": "Halp"})
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.
Parameters:
base (str): The base currency of the pair.
quote (str): The quote currency of the pair.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
pair_to_toggle = f"{base}/{quote}"
for x in running_instances:
if pair_to_toggle==x.pair:
if x.config_dict["check_old_long_price"]:
broker.logger.log_this("Check OFF",1,f"{base}/{quote}")
x.config_dict["check_old_long_price"] = False
return jsonify({"Success": "Old long price check turned OFF"})
else:
broker.logger.log_this("Check ON",1,f"{base}/{quote}")
x.config_dict["check_old_long_price"] = True
return jsonify({"Success": "Old long price check turned ON"})
except Exception as e:
broker.logger.log_this(f"Exception while toggling check_old_long_price: {e}",1,f"{base}{quote}")
return jsonify({"Error": "Halp"})
def unwrapped_switch_quote_currency(base,quote,new_quote):
'''
Switches the quote currency of a trader.
Parameters:
base (str): The base currency of the trader.
quote (str): The quote currency of the trader.
new_quote (str): The new quote currency to switch to.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
pair_to_switch = f"{base}/{quote}"
for trader in running_instances:
if pair_to_switch==trader.pair:
#Pause the trader
trader.pause = True
#Call x.switch_quote_currency
if trader.switch_quote_currency(new_quote)==1:
return jsonify({"Error": "Swap failed. Check log files for details."})
#Resume the trader
trader.pause = False
return jsonify({"Success": "Mission successful"})
return jsonify({"Error": "Trader not found"})
except Exception as e:
broker.logger.log_this(f"Exception while switching quote currency: {e}",1,f"{base}{quote}")
return jsonify({"Error": "Halp"})
def unwrapped_toggle_restart():
'''
Signals to an instance if it should restart a trader if there is an error.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
new_config = broker.get_config()
new_config["attempt_to_restart"] = not new_config["attempt_to_restart"]
broker.set_config(new_config)
return jsonify({"Success": "attempt_to_restart toggled successfully"})
def unwrapped_toggle_telegram():
'''
Switches on or off the Telegram notifications
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
new_config = broker.get_config()
new_config["telegram"] = not new_config["telegram"]
broker.set_config(new_config)
broker.logger.set_telegram_notifications(new_config["telegram"])
toggle = "ON" if new_config["telegram"] else "OFF"
return jsonify({"Success": f"Telegram successfully toggled {toggle}"})
def unwrapped_server_time():
'''
Returns the server time.
Returns:
jsonify: A jsonified dictionary containing the server time.
'''
return jsonify({"Time": time.time()})
def unwrapped_trader_time():
'''
Returns the time of the last trader instance lap.
Returns:
jsonify: A jsonified dictionary containing the time of the last trader instance lap.
'''
try:
return jsonify({"Time": max(x.last_time_seen for x in running_instances)})
except Exception as e:
broker.logger.log_this(f"Exception while retrieving trader_time: {e}",1)
return jsonify({"Error": str(e)})
def unwrapped_loop_wait_time(wait_time):
'''
Modifies the amount of time an instance waits between laps.
Parameters:
wait_time (int): The new amount of time to wait between laps.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
broker.set_lap_time(wait_time)
broker.logger.log_this("Done!")
return jsonify({"Success": "Lap time modified successfully"})
def unwrapped_call_wait_time(wait_time):
'''
Modifies the time between some API calls and retries.
Parameters:
wait_time (float): The new amount of time to wait between calls and retries.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
broker.set_wait_time(wait_time)
broker.logger.log_this("Done!")
return jsonify({"Success": "Call wait time modified successfully"})
def unwrapped_edit_cooldown_multiplier(cooldown_multiplier):
'''
Modifies the broker's cooldown multiplier.
Parameters:
cooldown_multiplier (float): The new cooldown multiplier.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
old_value = broker.get_cooldown_multiplier()
broker.set_cooldown_multiplier(cooldown_multiplier)
broker.logger.log_this(f"Done! New cooldown multiplier changed from {old_value} seconds to {broker.get_cooldown_multiplier()} seconds.")
return jsonify({"Success": "Cooldown multiplier modified successfully"})
def unwrapped_reload_markets():
'''
Reloads the markets from the exchange.
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
broker.reload_markets()
return jsonify({"Success": "Markets reloaded successfully"})
except Exception as e:
broker.logger.log_this(f"Exception while reloading markets: {e}",1)
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:
for trader in running_instances:
if trader.pair==f"{base}/{quote}":
trader.reload_safety_order()
return jsonify({"Success": "Safety order reloaded successfully"})
except Exception as e:
broker.logger.log_this(f"Exception while reloading safety order: {e}",1)
return jsonify({"Error": "Safety order couldn't be reloaded"})
if __name__=="__main__":
#Logo
display_splashscreen()
#Setup some variables
instance_start_time = int(time.time())
last_market_reload = time.time()
reload_interval = 3600 #Market reload interval (in seconds)
#Loading config file
print(time.strftime("[%Y/%m/%d %H:%M:%S] | Loading config file..."))
try:
with open(sys.argv[1]) as f:
read_config = json.load(f)
except Exception as e:
print(e)
print("Wrong syntax. Correct syntax is 'python3 dcaruntime.py xxxxx.json', xxxxx.json being the config file.")
os._exit(1)
#Check for import or load
import_mode = True
if "--first_start" in sys.argv:
import_mode = False
print(time.strftime("[%Y/%m/%d %H:%M:%S] | Initializing in FIRST START MODE, press enter to start..."))
else:
print(time.strftime("[%Y/%m/%d %H:%M:%S] | Initializing in IMPORT MODE, press enter to start..."))
input()
#Load exchange config
exchange = set_exchange(read_config)
if exchange is None:
print("Error initializing exchange. Check spelling and/or the exchange configuration file.")
os._exit(1)
#Creating the broker object
print(time.strftime(f"[%Y/%m/%d %H:%M:%S] | Connecting to {str(exchange)}..."))
broker = exchange_wrapper.broker(exchange,read_config,sys.argv[1]) #Also passes the config filename
#Declaring some variables
running_instances = []
open_orders = []
instances_to_add = []
online_pairs = []
toggle_pauses = []
tickers = []
threads = []
screen_buffer = []
worker_status = {}
global_status = {
"name": broker.get_config()["exchange"].upper(),
"instance_uptime": 0,
"online_workers": [],
"paused_traders": [],
"version": version,
"ccxt_version": f"{ccxt.__version__}",
"config": broker.get_config()
}
#Remove some keys that should not be public
for item in ["bot_chatID", "bot_token", "key", "secret", "password"]:
global_status["config"].pop(item,None)
#Load valid API keys
valid_keys = load_keys_from_db("utils/api_credentials.db")
#Initialize instances
if not import_mode:
toggle = input(f"This will initialize {len(broker.get_pairs())} instances, proceed? (Y/n) ")
if toggle not in ["Y","y",""]:
broker.logger.log_this("Aborting initialization",2)
os._exit(1)
#broker.logger.log_this(f"Initializing {len(broker.get_pairs())} instances",2)
for x in broker.get_pairs():
initialize_instance(x)
else:
toggle = input(f"This will import {len(broker.get_pairs())} instances, proceed? (Y/n) ")
if toggle not in ["Y","y",""]:
broker.logger.log_this("Aborting import",2)
os._exit(1)
#broker.logger.log_this(f"Importing {len(broker.get_pairs())} instances",2)
for x in broker.get_pairs():
import_instance(x)
broker.logger.log_this(f"All instances imported!",2)
#Threads to run: main loop and flask api
main_threads = [Thread(target=main_loop,args=()),Thread(target=run_API,args=())]
#Iterate indefinitely:
for m in main_threads:
m.start()