2271 lines
79 KiB
Python
2271 lines
79 KiB
Python
import datetime
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import logging
|
|
from threading import Thread
|
|
from waitress import serve
|
|
|
|
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 = "2025.01.31"
|
|
|
|
'''
|
|
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,
|
|
"generated_at": int(time.time())
|
|
}
|
|
|
|
|
|
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
|
|
})
|
|
elif config["exchange"]=="bybit":
|
|
exchange_class = getattr(ccxt, "bybit")
|
|
exchange = exchange_class({
|
|
"apiKey": config["key"],
|
|
"secret": config["secret"],
|
|
"timeout": timeout,
|
|
"enableRateLimit": True
|
|
})
|
|
exchange.options["acknowledged"] = True
|
|
if config["is_sandbox"]:
|
|
exchange.set_sandbox_mode(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.get_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.get_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.get_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.get_status_dict():
|
|
#screen_buffer.append(x.status_dict["status_string"])
|
|
screen_buffer.append(str(x))
|
|
for x in running_instances:
|
|
if not x.is_short and "status_string" in x.get_status_dict():
|
|
#screen_buffer.append(x.status_dict["status_string"])
|
|
screen_buffer.append(str(x))
|
|
|
|
#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.get_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("/switch_to_long_price", methods=["GET"])
|
|
def return_switch_price():
|
|
'''
|
|
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_switch_to_long_price(base,quote)
|
|
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("/get_log_list", methods=['GET'])
|
|
def get_log_list():
|
|
'''
|
|
GET request
|
|
|
|
Parameters:
|
|
coin: str
|
|
'''
|
|
|
|
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
|
return unwrapped_get_log_list()
|
|
return jsonify({'Error': 'API key invalid'}), 401
|
|
|
|
|
|
@base_api.route("/get_balance", methods=['GET'])
|
|
def get_balance():
|
|
'''
|
|
GET request
|
|
|
|
Parameters:
|
|
coin: str
|
|
'''
|
|
|
|
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
|
coin = request.args.get("coin")
|
|
return unwrapped_get_balance(coin)
|
|
return jsonify({'Error': 'API key invalid'}), 401
|
|
|
|
|
|
@base_api.route("/get_deals_cache", methods=['GET'])
|
|
def get_deals_cache():
|
|
'''
|
|
GET request
|
|
|
|
Parameters:
|
|
None
|
|
'''
|
|
|
|
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
|
return unwrapped_get_deals_cache()
|
|
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():
|
|
serve(base_api, host="0.0.0.0", port=broker.get_config()["port"], threads=32)
|
|
#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_trader()==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_trader()==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.get_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:
|
|
if "old_long" in x.get_status_dict():
|
|
return jsonify(x.get_status_dict()["old_long"])
|
|
return jsonify({"Error": "No old_long info found"})
|
|
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_switch_to_long_price(base,quote):
|
|
'''
|
|
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:
|
|
for x in running_instances:
|
|
if f"{base}/{quote}"==x.pair:
|
|
if "old_long" in x.get_status_dict():
|
|
#minimum_switch_price = (old_target - quote_already_in)/base_left
|
|
old_target = x.get_status_dict()["old_long"]["tp_price"]*x.get_status_dict()["old_long"]["tp_amount"]
|
|
base_left = x.get_status_dict()["old_long"]["tp_amount"]-x.get_status_dict()["base_bought"]
|
|
minimum_switch_price = (old_target - x.get_status_dict()["quote_spent"])/base_left
|
|
return jsonify({"switch_price": minimum_switch_price})
|
|
return jsonify({"Error": "No old_long info found"})
|
|
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():
|
|
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.get_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 is now OFF"})
|
|
else:
|
|
broker.logger.log_this("Autoswitch turned ON",1,f"{base}/{quote}")
|
|
x.config_dict["autoswitch"] = True
|
|
return jsonify({"Success": "Autoswitch is 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_get_log_list():
|
|
'''
|
|
Retrieves the last n entries from the broker's logger.
|
|
This list is kept on memory, to avoid having to read the log file every time.
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
jsonify: A jsonified dictionary containing the last n entries from the log file.
|
|
'''
|
|
return jsonify({"Logs": broker.logger.get_log_list()})
|
|
|
|
|
|
def unwrapped_get_deals_cache():
|
|
'''
|
|
Retrieves the last n entries from the broker's logger.
|
|
This list is kept on memory, to avoid having to read the log file every time.
|
|
|
|
Parameters:
|
|
None
|
|
|
|
Returns:
|
|
jsonify: A jsonified dictionary containing the last n entries from the log file.
|
|
'''
|
|
return jsonify({"Deals": broker.get_deals_cache()})
|
|
|
|
|
|
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"})
|
|
|
|
|
|
def unwrapped_get_balance(coin):
|
|
'''
|
|
Returns the balance of a given coin.
|
|
|
|
Parameters:
|
|
coin (str): The coin to get the balance of.
|
|
|
|
Returns:
|
|
dict: A dictionary containing the balance of the coin.
|
|
'''
|
|
|
|
try:
|
|
balance = broker.get_coins_balance(coin)
|
|
if balance not in [None,0]:
|
|
return jsonify({f"{coin}": balance[coin]['free']})
|
|
return jsonify({"Error": f"Balance query returned {balance}"})
|
|
except Exception as e:
|
|
broker.logger.log_this(f"Exception while querying balance: {e}",1)
|
|
return jsonify({"Error": "Balance could not be queried"})
|
|
|
|
|
|
|
|
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 main.py xxxxx.json (--first_start)', 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)
|
|
|
|
|
|
#Waitress logging
|
|
waitress_logger = logging.getLogger('waitress')
|
|
waitress_logger.setLevel(logging.INFO)
|
|
|
|
#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()
|
|
|