Compare commits

..

10 Commits

7 changed files with 311 additions and 14 deletions

View File

@ -746,6 +746,8 @@ class broker:
order_to_send = self.exchange.create_order(pair,"market",side,amount) order_to_send = self.exchange.create_order(pair,"market",side,amount)
time.sleep(self.wait_time) time.sleep(self.wait_time)
# Wait a bit more when dealing with Kucoin
return self.get_order(order_to_send["id"],pair) return self.get_order(order_to_send["id"],pair)
except Exception as e: except Exception as e:
self.logger.log_this(f"Exception in new_market_order: {e}",1,pair) self.logger.log_this(f"Exception in new_market_order: {e}",1,pair)

View File

@ -1687,7 +1687,6 @@ def unwrapped_last_call(base,quote):
try: try:
if f"{base}{quote}" in broker.get_pairs(): if f"{base}{quote}" in broker.get_pairs():
#read_config["pairs"].remove(base+quote)
for x in running_instances: for x in running_instances:
if f"{base}/{quote}"==x.pair: if f"{base}/{quote}"==x.pair:
x.stop_when_profit = not x.stop_when_profit x.stop_when_profit = not x.stop_when_profit
@ -1764,7 +1763,7 @@ def unwrapped_global_last_call():
''' '''
try: try:
if broker.get_pairs!=[]: if broker.get_pairs!=[]:
broker.clear_pairs() #broker.clear_pairs()
for x in running_instances: for x in running_instances:
x.stop_when_profit = True x.stop_when_profit = True
broker.logger.log_this("Modified flag",2,f"{x.base}/{x.quote}") broker.logger.log_this("Modified flag",2,f"{x.base}/{x.quote}")
@ -1910,11 +1909,11 @@ def unwrapped_toggle_autoswitch(base,quote):
if x.config_dict["autoswitch"]: if x.config_dict["autoswitch"]:
broker.logger.log_this("Autoswitch turned OFF",1,f"{base}/{quote}") broker.logger.log_this("Autoswitch turned OFF",1,f"{base}/{quote}")
x.config_dict["autoswitch"] = False x.config_dict["autoswitch"] = False
return jsonify({"Success": "Autoswitch it's now OFF"}) return jsonify({"Success": "Autoswitch is now OFF"})
else: else:
broker.logger.log_this("Autoswitch turned ON",1,f"{base}/{quote}") broker.logger.log_this("Autoswitch turned ON",1,f"{base}/{quote}")
x.config_dict["autoswitch"] = True x.config_dict["autoswitch"] = True
return jsonify({"Success": "Autoswitch it's now ON"}) return jsonify({"Success": "Autoswitch is now ON"})
except Exception as e: except Exception as e:
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,f"{base}{quote}") broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,f"{base}{quote}")
return jsonify({"Error": "Halp"}) return jsonify({"Error": "Halp"})

View File

@ -24,7 +24,6 @@ cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours'
last_60_days_rows = cursor.fetchall() last_60_days_rows = cursor.fetchall()
#Last 30 days query #Last 30 days query
#cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3,
cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3, cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3,
SUM(amount) AS total_amount SUM(amount) AS total_amount
FROM profits_table FROM profits_table
@ -47,6 +46,15 @@ cursor.execute("""SELECT strftime('%Y-%m', timestamp, 'unixepoch', '-3 hours') A
ORDER BY year_month_utc3;""") ORDER BY year_month_utc3;""")
last_n_months_rows = cursor.fetchall() last_n_months_rows = cursor.fetchall()
#Last n months query
cursor.execute("""SELECT strftime('%Y', timestamp, 'unixepoch', '-3 hours') AS year_utc3,
SUM(amount) AS total_amount
FROM profits_table
WHERE strftime('%s', 'now') - timestamp <= 60 * 30 * 24 * 60 * 60 -- 48 months in seconds
GROUP BY year_utc3
ORDER BY year_utc3;""")
last_n_years_rows = cursor.fetchall()
#Yearly totals #Yearly totals
cursor.execute("""SELECT strftime('%Y', timestamp, 'unixepoch', '-3 hours') AS year_utc3, cursor.execute("""SELECT strftime('%Y', timestamp, 'unixepoch', '-3 hours') AS year_utc3,
SUM(amount) AS total_amount SUM(amount) AS total_amount
@ -66,7 +74,13 @@ print("Last 18 months:")
print("-"*line_width) print("-"*line_width)
for row in last_n_months_rows[1:]: for row in last_n_months_rows[1:]:
print(f"{row[0]}: {round(row[1],2)}") print(f"{row[0]}: {round(row[1],2)}")
print("="*line_width)
print("Last 5 years:")
print("-"*line_width) print("-"*line_width)
for row in last_n_years_rows:
print(f"{row[0]}: {round(row[1],2)}")
print("-"*line_width)
print(f"Last 30 days average: {round(last_30_days[0][1]/30,2)}") print(f"Last 30 days average: {round(last_30_days[0][1]/30,2)}")
print(f"Last 7 days average: {round(last_7_days[0][1]/7,2)}") print(f"Last 7 days average: {round(last_7_days[0][1]/7,2)}")
cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3, cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3,
@ -125,6 +139,9 @@ for row in by_exchange:
if row[1]=="This Month": if row[1]=="This Month":
okex_amount = row[2] okex_amount = row[2]
#Close db
cursor.close()
total_amount = binance_amount+gateio_amount+kucoin_amount+okex_amount total_amount = binance_amount+gateio_amount+kucoin_amount+okex_amount
print(f"Binance: {round(binance_amount,2)} USDT ({round(binance_amount/total_amount*100,2)}%)") print(f"Binance: {round(binance_amount,2)} USDT ({round(binance_amount/total_amount*100,2)}%)")

View File

@ -1,6 +1,5 @@
Mandatory: Mandatory:
========= =========
0. Mobile app.
1. Stats webpage. 1. Stats webpage.
2. Maintain local orderbooks for each trading pair, which enables: 2. Maintain local orderbooks for each trading pair, which enables:
2a. Smart order pricing: Prioritization of fill speed over instant profit or vice versa 2a. Smart order pricing: Prioritization of fill speed over instant profit or vice versa

View File

@ -385,7 +385,9 @@ class trader:
self.status_dict["start_time"]=self.start_time self.status_dict["start_time"]=self.start_time
self.status_dict["deal_start_time"]=self.deal_start_time self.status_dict["deal_start_time"]=self.deal_start_time
self.status_dict["stop_when_profit"]=self.stop_when_profit self.status_dict["stop_when_profit"]=self.stop_when_profit
self.status_dict["autoswitch"]=self.config_dict["autoswitch"] self.status_dict["autoswitch"]=False
if "autoswitch" in self.config_dict:
self.status_dict["autoswitch"]=self.config_dict["autoswitch"]
except Exception as e: except Exception as e:
self.broker.logger.log_this(f"Can't update status dictionary. Exception: {e}",1,self.pair) self.broker.logger.log_this(f"Can't update status dictionary. Exception: {e}",1,self.pair)
@ -1689,10 +1691,8 @@ class trader:
if old_target-self.status_dict["quote_spent"]>0 and base_left>0 and minimum_switch_price<low_price: if old_target-self.status_dict["quote_spent"]>0 and base_left>0 and minimum_switch_price<low_price:
low_boundary_color = bright_green low_boundary_color = bright_green
low_boundary = '{:.20f}'.format(minimum_switch_price)[:decimals].center(decimals) low_boundary = '{:.20f}'.format(minimum_switch_price)[:decimals].center(decimals)
#Logic for multiplier if self.status_dict["price"]!=0:
#When adding a trader, this line always throws an exception since status_dict["price"] is not yet populated multiplier = int(self.status_dict["old_long"]["tp_price"]/self.status_dict["price"])
percentage_to_switch = (self.status_dict["old_long"]["tp_price"]-self.status_dict["price"])*100/self.status_dict["price"]
multiplier = int(percentage_to_switch/100)+1
except Exception as e: except Exception as e:
print(e) print(e)

View File

@ -4,6 +4,7 @@ import json
import credentials import credentials
try: try:
earn_api_key = credentials.get_credentials("earn_api_key")["key"]
if sys.argv[1]=="--testnet": if sys.argv[1]=="--testnet":
is_testnet = True is_testnet = True
string_to_add = "TESTNET " string_to_add = "TESTNET "
@ -25,6 +26,8 @@ except Exception as e:
headers = {'X-API-KEY': api_key} headers = {'X-API-KEY': api_key}
earn_headers = {'X-API-KEY': earn_api_key}
command_list = f'''{string_to_add}COMMANDS: command_list = f'''{string_to_add}COMMANDS:
INSTANCE INSTANCE
@ -35,6 +38,14 @@ INSTANCE
13) paused_traders 14) fetch_log 15) edit_cooldown_multiplier 13) paused_traders 14) fetch_log 15) edit_cooldown_multiplier
16) backtests 17) get_balance 16) backtests 17) get_balance
EARN
31) toggle_pause 32) get_step_size 33) set_step_size
34) get_percentage 35) set_percentage 36) get_time_between_subscriptions
37) set_time_between_subscriptions 38) get_time_between_redemptions
39) set_time_between_redemptions 40) get_minimum_amount_in_trading_account
41) set_minimum_amount_in_trading_account 42) get_last_subscription
43) get_last_redemption 44) get_total_balance
TRADERS TRADERS
51) worker_status 52) get_all_worker_status 51) worker_status 52) get_all_worker_status
53) add_pair 54) remove_pair 55) restart_pair 53) add_pair 54) remove_pair 55) restart_pair
@ -99,6 +110,10 @@ if __name__=="__main__":
#print("Invalid input") #print("Invalid input")
#sys.exit() #sys.exit()
port = exchanges[selection] port = exchanges[selection]
earn_broker = port[1:]
if earn_broker=="okex":
earn_broker="okx"
print("DCAv2 COMMANDER") print("DCAv2 COMMANDER")
@ -132,6 +147,9 @@ if __name__=="__main__":
# break # break
selection = select_exchange(exchanges) selection = select_exchange(exchanges)
port = exchanges[selection] port = exchanges[selection]
earn_broker = port[1:]
if earn_broker=="okex":
earn_broker="okx"
print(f"New exchange selected: {selection}") print(f"New exchange selected: {selection}")
@ -308,6 +326,128 @@ if __name__=="__main__":
print(json.loads(requests.get(url,headers=headers).content)) print(json.loads(requests.get(url,headers=headers).content))
input("Press ENTER to continue ") input("Press ENTER to continue ")
######################
######## EARN ########
######################
elif command==31:
print("toggle_pause interrupts or resume the subcription and redemption of funds")
if input(f"This will toggle the subscription and redemption of funds on {port}. Are you sure? (Y/n) ") in ["Y","y",""]:
url = f"{base_url}/earn/toggle_pause"
parameters = {"broker": earn_broker}
print(json.loads(requests.post(url,headers=earn_headers, json=parameters).content))
input("Press ENTER to continue ")
elif command==32:
print("get_step_size returns the step size")
url = f"{base_url}/earn/get_step_size?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
elif command==33:
print("set_step_size sets the step size")
new_step_size = input("New step size? ")
if not validate_float_or_int(new_step_size):
print("Invalid step size")
break
if input(f"This will set the step size to {new_step_size}. Are you sure? (Y/n) ") in ["Y","y",""]:
url = f"{base_url}/earn/set_step_size"
parameters = {"broker": earn_broker, "new_step_size": new_step_size}
print(json.loads(requests.post(url,headers=earn_headers, json=parameters).content))
input("Press ENTER to continue ")
elif command==34:
print("get_percentage displays the percentage of funds to be allocated to earn")
url = f"{base_url}/earn/get_percentage?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
elif command==35:
print("set_percentage sets the percentage of funds to be allocated to earn")
new_percentage = input("New percentage? ")
if not validate_float_or_int(new_percentage):
print("Invalid percentage")
break
if input(f"This will set the percentage to {new_percentage}. Are you sure? (Y/n) ") in ["Y","y",""]:
url = f"{base_url}/earn/set_percentage"
parameters = {"broker": earn_broker, "new_percentage": new_percentage}
print(json.loads(requests.post(url,headers=earn_headers, json=parameters).content))
input("Press ENTER to continue ")
elif command==36:
print("get_time_between_subscriptions displays the time to wait between subscriptions")
url = f"{base_url}/earn/get_time_between_subscriptions?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
elif command==37:
print("set_time_between_subscriptions sets the time to wait between subscriptions")
new_time_between_subscriptions = input("New time between subscriptions? ")
if not validate_int(new_time_between_subscriptions):
print("Invalid time")
break
if input(f"This will set the time to wait between subscriptions to {new_time_between_subscriptions}. Are you sure? (Y/n) ") in ["Y","y",""]:
url = f"{base_url}/earn/set_time_between_subscriptions"
parameters = {"broker": earn_broker, "new_time_between_subscriptions": new_time_between_subscriptions}
print(json.loads(requests.post(url,headers=earn_headers, json=parameters).content))
input("Press ENTER to continue ")
elif command==38:
print("get_time_between_redemptions displays the time to wait between redemptions")
url = f"{base_url}/earn/get_time_between_redemptions?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
elif command==39:
print("set_time_between_redemptions sets the time to wait between redemptions")
new_time_between_redemptions = input("New time between redemptions? ")
if not validate_int(new_time_between_redemptions):
print("Invalid time")
break
if input(f"This will set the time to wait between redemptions to {new_time_between_redemptions}. Are you sure? (Y/n) ") in ["Y","y",""]:
url = f"{base_url}/earn/set_time_between_redemptions"
parameters = {"broker": earn_broker, "new_time_between_redemptions": new_time_between_redemptions}
print(json.loads(requests.post(url,headers=earn_headers, json=parameters).content))
input("Press ENTER to continue ")
elif command==40:
print("get_minimum_amount_in_trading_account displays the minimum amount of funds that always have to exist in the trading account")
url = f"{base_url}/earn/get_minimum_amount_in_trading_account?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
elif command==41:
print("set_minimum_amount_in_trading_account sets the minimum amount of funds that always have to exist in the trading account")
new_minimum_amount_in_trading_account = input("New minimum amount in trading account? ")
if not validate_int(new_minimum_amount_in_trading_account):
print("Invalid amount")
break
if input(f"This will set the minimum amount of funds that always have to exist in the trading account to {new_minimum_amount_in_trading_account}. Are you sure? (Y/n) ") in ["Y","y",""]:
url = f"{base_url}/earn/set_minimum_amount_in_trading_account"
parameters = {"broker": earn_broker, "new_minimum_amount_in_trading_account": new_minimum_amount_in_trading_account}
print(json.loads(requests.post(url,headers=earn_headers, json=parameters).content))
input("Press ENTER to continue ")
elif command==42:
print("get_last_subscription display the last subscription")
url = f"{base_url}/earn/get_last_subscription?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
elif command==43:
print("get_last_redemptions displays the last redemption")
url = f"{base_url}/earn/get_last_redemption?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
elif command==44:
print("get_total_balance displays the trading account balance and the earning account balance")
url = f"{base_url}/earn/get_total_balance?broker={earn_broker}"
print(json.loads(requests.get(url,headers=earn_headers).content))
input("Press ENTER to continue ")
###################### ######################
####### TRADER ####### ####### TRADER #######
###################### ######################

View File

@ -4,6 +4,7 @@ import datetime
import time import time
import ccxt import ccxt
import credentials import credentials
import calendar
import requests import requests
import logging import logging
from flask import Flask, jsonify, request from flask import Flask, jsonify, request
@ -59,6 +60,126 @@ def load_keys_from_db(file_name):
return valid_keys return valid_keys
def profit_report():
##Queries
connection = sqlite3.connect(profits_database)
cursor = connection.cursor()
#Last 60 days query
cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3,
SUM(amount) AS total_amount
FROM profits_table
WHERE strftime('%s', 'now') - timestamp <= 60 * 24 * 60 * 60 -- 60 days in seconds
GROUP BY day_utc3
ORDER BY day_utc3;""")
last_60_days_rows = cursor.fetchall()
#Last 30 days query
#cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3,
cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3,
SUM(amount) AS total_amount
FROM profits_table
WHERE strftime('%s', 'now') - timestamp <= 30 * 24 * 60 * 60 -- 30 days in seconds;""")
last_30_days = cursor.fetchall()
#Last 7 days query
cursor.execute("""SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch', '-3 hours') AS day_utc3,
SUM(amount) AS total_amount
FROM profits_table
WHERE strftime('%s', 'now') - timestamp <= 7 * 24 * 60 * 60 -- 7 days in seconds;""")
last_7_days = cursor.fetchall()
#Last n months query
cursor.execute("""SELECT strftime('%Y-%m', timestamp, 'unixepoch', '-3 hours') AS year_month_utc3,
SUM(amount) AS total_amount
FROM profits_table
WHERE strftime('%s', 'now') - timestamp <= 18 * 30 * 24 * 60 * 60 -- 18 months in seconds
GROUP BY year_month_utc3
ORDER BY year_month_utc3;""")
last_n_months_rows = cursor.fetchall()
#Yearly totals
cursor.execute("""SELECT strftime('%Y', timestamp, 'unixepoch', '-3 hours') AS year_utc3,
SUM(amount) AS total_amount
FROM profits_table
WHERE strftime('%s', 'now') - timestamp <= 24 * 365 * 60 * 60 -- 365 days in seconds
GROUP BY year_utc3
ORDER BY year_utc3;""")
yearly_totals = cursor.fetchall()
#Per exchange
cursor.execute("""SELECT
exchange_name,
CASE
WHEN strftime('%Y-%m', timestamp, 'unixepoch', '-3 hours') = strftime('%Y-%m', 'now', 'localtime') THEN 'This Month'
WHEN strftime('%Y-%m', timestamp, 'unixepoch', '-3 hours') = strftime('%Y-%m', 'now', 'localtime', '-1 month') THEN 'Last Month'
ELSE 'Other Months'
END AS month_group,
SUM(amount) AS total_amount
FROM
profits_table
WHERE
strftime('%s', 'now') - timestamp <= 60 * 24 * 60 * 60 -- 60 days in seconds
GROUP BY
exchange_name, month_group
ORDER BY
exchange_name, month_group;""")
per_exchange = cursor.fetchall()
#Close db
cursor.close()
#Projection calculation
days_in_month = calendar.monthrange(datetime.date.today().year, datetime.date.today().month)[1]
daily_combined_media = (last_30_days[0][1]/30+last_7_days[0][1]/7)/2
current_amount = last_n_months_rows[-1][1]
days_past_this_month = int(last_60_days_rows[-1][0][8:10])
#Per exchange
binance_amount = 0
gateio_amount = 0
kucoin_amount = 0
okex_amount = 0
for row in per_exchange:
if row[0]=="binance":
if row[1]=="This Month":
binance_amount = row[2]
elif row[0]=="gateio":
if row[1]=="This Month":
gateio_amount = row[2]
elif row[0]=="kucoin":
if row[1]=="This Month":
kucoin_amount = row[2]
elif row[0]=="okex":
if row[1]=="This Month":
okex_amount = row[2]
total_amount = binance_amount+gateio_amount+kucoin_amount+okex_amount
last_60_days_result = {row[0]: round(row[1],2) for row in last_60_days_rows}
last_18_months_result = {row[0]: round(row[1],2) for row in last_n_months_rows}
last_30_days_average = last_30_days[0][1]/30
last_7_days_average = last_7_days[0][1]/7
this_month_projection = current_amount + daily_combined_media*(days_in_month-days_past_this_month)
binance_percentage = binance_amount/total_amount*100
gateio_percentage = gateio_amount/total_amount*100
kucoin_percentage = kucoin_amount/total_amount*100
okex_percentage = okex_amount/total_amount*100
return {"Last 60 days": last_60_days_result,
"Last 18 months": last_18_months_result,
"Last 30 days average": last_30_days_average,
"Last 7 days average": last_7_days_average,
"This month projection": this_month_projection,
"Binance": binance_amount,
"Binance percentage": binance_percentage,
"Gateio": gateio_amount,
"Gateio percentage": gateio_percentage,
"Kucoin": kucoin_amount,
"Kucoin percentage": kucoin_percentage,
"OKX": okex_amount,
"OKX percentage": okex_percentage,
"Total profit": total_amount}
def query_total_profit(pair=None): def query_total_profit(pair=None):
''' '''
Returns total profit of the trading pair. Returns total profit of the trading pair.
@ -320,6 +441,23 @@ def fetch_backtests():
return jsonify({'Error': 'API key invalid'}), 401 return jsonify({'Error': 'API key invalid'}), 401
@stats_api.route("/fetch_profit_report")
def fetch_profit_report():
'''
GET request
Parameters: None
Returns: JSON object with profit report data
'''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try:
return jsonify(profit_report())
except Exception as e:
print(e)
return jsonify({"Error": f"{e}"})
return jsonify({'Error': 'API key invalid'}), 401
@stats_api.route("/clear_caches") @stats_api.route("/clear_caches")
def clear_hashes(): def clear_hashes():
global hashes_db global hashes_db
@ -395,6 +533,8 @@ def fetch_full_log():
''' '''
GET request GET request
Parameters: 'exchange_name" -> string Parameters: 'exchange_name" -> string
It trims the full log to 200 lines, to avoid sending too much data to the client.
''' '''
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys: if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
try: try:
@ -402,11 +542,11 @@ def fetch_full_log():
width = 0 width = 0
last_lines,amount_of_lines = last_n_lines(f"../logs/{exchange_name}.log",width,0,full_log=True) last_lines,amount_of_lines = last_n_lines(f"../logs/{exchange_name}.log",width,0,full_log=True)
if not cache_requests: if not cache_requests:
return jsonify({"line": last_lines, "amount_of_lines": amount_of_lines}) return jsonify({"line": last_lines[-200:], "amount_of_lines": amount_of_lines})
response_hash = hash(str({"line": last_lines, "amount_of_lines": amount_of_lines})) response_hash = hash(str({"line": last_lines, "amount_of_lines": amount_of_lines}))
if hashes_db["fetch_full_log"]!=response_hash: if hashes_db["fetch_full_log"]!=response_hash:
hashes_db["fetch_full_log"] = response_hash hashes_db["fetch_full_log"] = response_hash
return jsonify({"line": last_lines, "amount_of_lines": amount_of_lines}) return jsonify({"line": last_lines[-200:], "amount_of_lines": amount_of_lines})
return jsonify({"no_changes": True}) return jsonify({"no_changes": True})
except Exception as e: except Exception as e:
print(e) print(e)