Classes implemented in python and javascript. UML class diagram. Rough sequence uml. TODO: local file getting dirty from refresh. Signals implemented. Strategies implemented. Trades are mostly implemented.
This commit is contained in:
parent
5d942a9c2c
commit
934a66012d
|
|
@ -95,6 +95,12 @@ class Signals:
|
||||||
def get_signals(self):
|
def get_signals(self):
|
||||||
return self.signals
|
return self.signals
|
||||||
|
|
||||||
|
def get_signal_by_name(self, name):
|
||||||
|
for signal in self.signals:
|
||||||
|
if signal.name == name:
|
||||||
|
return signal
|
||||||
|
return None
|
||||||
|
|
||||||
def new_signal(self, data):
|
def new_signal(self, data):
|
||||||
self.signals.append(Signal(**data))
|
self.signals.append(Signal(**data))
|
||||||
|
|
||||||
|
|
|
||||||
155
Strategies.py
155
Strategies.py
|
|
@ -1,15 +1,160 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class Strategies:
|
class Strategies:
|
||||||
def __init__(self, loaded_strats):
|
def __init__(self, loaded_strats, trades):
|
||||||
self.strat_list = loaded_strats
|
self.strat_list = loaded_strats
|
||||||
|
self.trades = trades
|
||||||
|
|
||||||
def new_strategy(self, data):
|
def new_strategy(self, data):
|
||||||
self.strat_list.append(data)
|
self.strat_list.append(data)
|
||||||
|
|
||||||
def delete_strategy(self, name):
|
def delete_strategy(self, name):
|
||||||
for obj in self.strat_list:
|
obj = self.get_strategy_by_name(name)
|
||||||
if obj['name'] == name:
|
if obj:
|
||||||
self.strat_list.remove(obj)
|
self.strat_list.remove(obj)
|
||||||
break
|
|
||||||
|
|
||||||
def get_strategies(self):
|
def get_strategies(self):
|
||||||
return self.strat_list
|
return self.strat_list
|
||||||
|
|
||||||
|
def get_strategy_by_name(self, name):
|
||||||
|
for obj in self.strat_list:
|
||||||
|
if obj['name'] == name:
|
||||||
|
return obj
|
||||||
|
return False
|
||||||
|
|
||||||
|
def execute_cmd(self, strategy, action, cmd):
|
||||||
|
order_type = 'LIMIT'
|
||||||
|
if action == 'open_position':
|
||||||
|
# Attempt to create the trade.
|
||||||
|
trade_id = self.trades.new_trade(strategy['symbol'], cmd, order_type, strategy['trade_amount'])
|
||||||
|
# If the trade didn't fail.
|
||||||
|
if trade_id is not False:
|
||||||
|
# Set the active flag in strategy.
|
||||||
|
strategy['active'] = True
|
||||||
|
strategy['current_position'] += strategy['trade_amount']
|
||||||
|
strategy['trades'].append(trade_id)
|
||||||
|
return 'position_opened'
|
||||||
|
else:
|
||||||
|
print('Failed to place trade')
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
if (action == 'stop_loss') or (action == 'take_profit'):
|
||||||
|
if action == 'stop_loss':
|
||||||
|
order_type = 'MARKET'
|
||||||
|
# Attempt to create the trade.
|
||||||
|
trade = self.trades.new_trade(strategy['symbol'], cmd, order_type, strategy['current_position'])
|
||||||
|
# If the trade didn't fail.
|
||||||
|
if trade is not False:
|
||||||
|
# Set the active flag in strategy.
|
||||||
|
strategy['active'] = False
|
||||||
|
strategy['current_position'] = 0
|
||||||
|
return 'position_closed'
|
||||||
|
else:
|
||||||
|
print('Failed to place trade')
|
||||||
|
return 'failed'
|
||||||
|
print('Strategies.execute_cmd: Invalid action received.')
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def update(self, signals):
|
||||||
|
"""
|
||||||
|
Receives a reference to updated signal data. Loops through all
|
||||||
|
published strategies and evaluates conditions against the data.
|
||||||
|
This function returns a list of strategies and action commands.
|
||||||
|
"""
|
||||||
|
# Object containing data to return to function caller.
|
||||||
|
actions = {}
|
||||||
|
# Loop through all the published strategies.
|
||||||
|
for strategy in self.strat_list:
|
||||||
|
# Process any take_profit strategy.
|
||||||
|
if strategy['type'] == 'take_profit':
|
||||||
|
action, cmd = self.eval_tp_stg(strategy, signals)
|
||||||
|
if action == 'do_nothing':
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Execute the command.
|
||||||
|
actions[strategy['name']] = self.execute_cmd(strategy, action, cmd)
|
||||||
|
else:
|
||||||
|
print(f"Strategy.update: Strategy of type {strategy['type']} - not yet implemented.")
|
||||||
|
if len(actions) != 0:
|
||||||
|
return actions
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def eval_tp_stg(self, strategy, signals):
|
||||||
|
"""
|
||||||
|
:param strategy: str: The strategy to evaluate.
|
||||||
|
:param signals: Signals: A reference to an object that handles current signal states.
|
||||||
|
:return action: Action required based on evaluation. format{cmd:str, amount:real, margin:int}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def condition_satisfied(sig_name, vlue):
|
||||||
|
signal = signals.get_signal_by_name(sig_name)
|
||||||
|
if vlue == json.dumps(signal.state):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def all_conditions_met(conditions):
|
||||||
|
if len(conditions) < 1:
|
||||||
|
print(f"no trade-in conditions supplied: {strategy['name']}")
|
||||||
|
return False
|
||||||
|
# Evaluate all conditions and return false if any are un-met.
|
||||||
|
for trigger_signal in conditions.keys():
|
||||||
|
trigger_value = conditions[trigger_signal]
|
||||||
|
# Compare this signal's state with the trigger_value
|
||||||
|
if not condition_satisfied(trigger_signal, trigger_value):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def trade_out_condition_met(condition_type):
|
||||||
|
if strategy[condition_type]['typ'] == 'conditional':
|
||||||
|
signal_name = strategy[condition_type]['trig']
|
||||||
|
signal_value = strategy[condition_type]['val']
|
||||||
|
if condition_satisfied(signal_name, signal_value):
|
||||||
|
# If the condition is met trade-out.
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if strategy[condition_type]['typ'] != 'value':
|
||||||
|
raise ValueError('trade_out_condition_met: invalid condition_type')
|
||||||
|
if condition_type == 'take_profit':
|
||||||
|
# If the profit condition is met send command to take profit.
|
||||||
|
if strategy['gross_profit'] > strategy['take_profit']['val']:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# If the loss condition is met, return a trade-out command.
|
||||||
|
if strategy['gross_loss'] < strategy['stop_loss']['val']:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
trade_in_cmd = strategy['side']
|
||||||
|
if strategy['side'] == 'buy':
|
||||||
|
trade_out_cmd = 'sell'
|
||||||
|
else:
|
||||||
|
trade_out_cmd = 'buy'
|
||||||
|
|
||||||
|
# If trade-in conditions are met.
|
||||||
|
if all_conditions_met(strategy['trd_in_conds']):
|
||||||
|
# If the new trade wouldn't exceed max_position. Return a trade-in command.
|
||||||
|
proposed_position_size = strategy['current_position'] + strategy['trade_amount']
|
||||||
|
if proposed_position_size < strategy['max_position']:
|
||||||
|
return 'enter_position', trade_in_cmd
|
||||||
|
|
||||||
|
# If strategy is active test the take-profit or stop-loss conditions.
|
||||||
|
if strategy['active']:
|
||||||
|
# Conditional take-profit trades-out if a signals equals a set value.
|
||||||
|
if trade_out_condition_met('take_profit'):
|
||||||
|
return 'take_profit', trade_out_cmd
|
||||||
|
|
||||||
|
# Conditional stop-loss trades-outs if a signals value equals a set value.
|
||||||
|
if trade_out_condition_met('stop_loss'):
|
||||||
|
return 'stop_loss', trade_out_cmd
|
||||||
|
|
||||||
|
# No conditions were met.
|
||||||
|
print('Strategies were updated and nothing to do.')
|
||||||
|
return 'do_nothing', 'nothing'
|
||||||
|
|
|
||||||
81
app.py
81
app.py
|
|
@ -4,15 +4,17 @@ from flask import Flask, render_template, request, redirect, jsonify
|
||||||
from flask_cors import cross_origin
|
from flask_cors import cross_origin
|
||||||
from binance.enums import *
|
from binance.enums import *
|
||||||
from flask_sock import Sock
|
from flask_sock import Sock
|
||||||
# Handles all server side data
|
|
||||||
import data as bt
|
# Handles all server side data and interactions.
|
||||||
# *NOT DONE YET*
|
from data import BrighterData
|
||||||
import trade
|
|
||||||
|
|
||||||
# Define app
|
# Define app
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
sock = Sock(app)
|
sock = Sock(app)
|
||||||
|
|
||||||
|
# This object maintains all the application and historical data.
|
||||||
|
# Access to server, local storage, other classes go through here.
|
||||||
|
app_data = BrighterData()
|
||||||
|
|
||||||
# app.config['SECRET_KEY'] = 'The quick brown fox jumps over the lazy dog'
|
# app.config['SECRET_KEY'] = 'The quick brown fox jumps over the lazy dog'
|
||||||
# app.config['CORS_HEADERS'] = 'Content-Type'
|
# app.config['CORS_HEADERS'] = 'Content-Type'
|
||||||
|
|
@ -21,8 +23,8 @@ sock = Sock(app)
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
# Passes data into an HTML template and serves it to a locally hosted server
|
# Passes data into an HTML template and serves it to a locally hosted server
|
||||||
rendered_data = bt.app_data.get_rendered_data()
|
rendered_data = app_data.get_rendered_data()
|
||||||
js_data = bt.app_data.get_js_init_data()
|
js_data = app_data.get_js_init_data()
|
||||||
return render_template('index.html',
|
return render_template('index.html',
|
||||||
title=rendered_data['title'],
|
title=rendered_data['title'],
|
||||||
my_balances=rendered_data['my_balances'],
|
my_balances=rendered_data['my_balances'],
|
||||||
|
|
@ -43,7 +45,7 @@ def ws(sock):
|
||||||
if msg_obj['message_type'] == 'candle_data':
|
if msg_obj['message_type'] == 'candle_data':
|
||||||
# Send the candle to the BrighterData_obj
|
# Send the candle to the BrighterData_obj
|
||||||
# and forward any returned data to the client.
|
# and forward any returned data to the client.
|
||||||
r_data = bt.app_data.received_cdata(msg_obj['data'])
|
r_data = app_data.received_cdata(msg_obj['data'])
|
||||||
if r_data:
|
if r_data:
|
||||||
resp = {
|
resp = {
|
||||||
"reply": "updates",
|
"reply": "updates",
|
||||||
|
|
@ -53,7 +55,7 @@ def ws(sock):
|
||||||
|
|
||||||
if msg_obj['message_type'] == 'request':
|
if msg_obj['message_type'] == 'request':
|
||||||
if msg_obj['data'] == 'signals':
|
if msg_obj['data'] == 'signals':
|
||||||
signals = bt.app_data.get_signals()
|
signals = app_data.get_signals()
|
||||||
if signals:
|
if signals:
|
||||||
resp = {
|
resp = {
|
||||||
"reply": "signals",
|
"reply": "signals",
|
||||||
|
|
@ -62,7 +64,7 @@ def ws(sock):
|
||||||
resp = json.dumps(resp)
|
resp = json.dumps(resp)
|
||||||
sock.send(resp)
|
sock.send(resp)
|
||||||
elif msg_obj['data'] == 'strategies':
|
elif msg_obj['data'] == 'strategies':
|
||||||
strategies = bt.app_data.get_strategies()
|
strategies = app_data.get_strategies()
|
||||||
if strategies:
|
if strategies:
|
||||||
resp = {
|
resp = {
|
||||||
"reply": "strategies",
|
"reply": "strategies",
|
||||||
|
|
@ -75,10 +77,10 @@ def ws(sock):
|
||||||
print(msg_obj['data'])
|
print(msg_obj['data'])
|
||||||
|
|
||||||
if msg_obj['message_type'] == 'delete_signal':
|
if msg_obj['message_type'] == 'delete_signal':
|
||||||
bt.app_data.delete_signal(msg_obj['data'])
|
app_data.delete_signal(msg_obj['data'])
|
||||||
|
|
||||||
if msg_obj['message_type'] == 'delete_strategy':
|
if msg_obj['message_type'] == 'delete_strategy':
|
||||||
bt.app_data.delete_strategy(msg_obj['data'])
|
app_data.delete_strategy(msg_obj['data'])
|
||||||
|
|
||||||
if msg_obj['message_type'] == 'reply':
|
if msg_obj['message_type'] == 'reply':
|
||||||
print(msg_obj['rep'])
|
print(msg_obj['rep'])
|
||||||
|
|
@ -87,7 +89,7 @@ def ws(sock):
|
||||||
if msg_obj['message_type'] == 'new_signal':
|
if msg_obj['message_type'] == 'new_signal':
|
||||||
# Send the data to the BrighterData_obj
|
# Send the data to the BrighterData_obj
|
||||||
# and forward any returned data to the client.
|
# and forward any returned data to the client.
|
||||||
r_data = bt.app_data.received_new_signal(msg_obj['data'])
|
r_data = app_data.received_new_signal(msg_obj['data'])
|
||||||
if r_data:
|
if r_data:
|
||||||
resp = {
|
resp = {
|
||||||
"reply": "signal_created",
|
"reply": "signal_created",
|
||||||
|
|
@ -99,7 +101,7 @@ def ws(sock):
|
||||||
if msg_obj['message_type'] == 'new_strategy':
|
if msg_obj['message_type'] == 'new_strategy':
|
||||||
# Send the data to the BrighterData_obj
|
# Send the data to the BrighterData_obj
|
||||||
# and forward any returned data to the client.
|
# and forward any returned data to the client.
|
||||||
r_data = bt.app_data.received_new_strategy(msg_obj['data'])
|
r_data = app_data.received_new_strategy(msg_obj['data'])
|
||||||
if r_data:
|
if r_data:
|
||||||
resp = {
|
resp = {
|
||||||
"reply": "strategy_created",
|
"reply": "strategy_created",
|
||||||
|
|
@ -126,19 +128,20 @@ def ws(sock):
|
||||||
@app.route('/buy', methods=['POST'])
|
@app.route('/buy', methods=['POST'])
|
||||||
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
||||||
def buy():
|
def buy():
|
||||||
print(request.form) # Debug ******
|
print('This buy route is currently not being used.')
|
||||||
trade.order(
|
# app_data.trades.new_trade(
|
||||||
symbol=request.form['symbol'], side=SIDE_BUY,
|
# symbol=request.form['symbol'], side=SIDE_BUY,
|
||||||
type=ORDER_TYPE_MARKET, quantity=request.form['quantity'])
|
# type=ORDER_TYPE_MARKET, quantity=request.form['quantity'])
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/sell', methods=['POST'])
|
@app.route('/sell', methods=['POST'])
|
||||||
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
||||||
def sell():
|
def sell():
|
||||||
trade.order(
|
print('This sell route is currently not being used.')
|
||||||
symbol=request.form['symbol'], side=SIDE_SELL,
|
# app_data.trades.new_trade(
|
||||||
type=ORDER_TYPE_MARKET, quantity=request.form['quantity'])
|
# symbol=request.form['symbol'], side=SIDE_SELL,
|
||||||
|
# type=ORDER_TYPE_MARKET, quantity=request.form['quantity'])
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -148,10 +151,10 @@ def settings():
|
||||||
setting = request.form['setting']
|
setting = request.form['setting']
|
||||||
if setting == 'interval':
|
if setting == 'interval':
|
||||||
interval_state = request.form['timeframe']
|
interval_state = request.form['timeframe']
|
||||||
bt.app_data.config.chart_interval = interval_state
|
app_data.config.chart_interval = interval_state
|
||||||
elif setting == 'trading_pair':
|
elif setting == 'trading_pair':
|
||||||
trading_pair = request.form['trading_pair']
|
trading_pair = request.form['trading_pair']
|
||||||
bt.app_data.config.trading_pair = trading_pair
|
app_data.config.trading_pair = trading_pair
|
||||||
elif setting == 'toggle_indicator':
|
elif setting == 'toggle_indicator':
|
||||||
# Get a list of indicators to enable
|
# Get a list of indicators to enable
|
||||||
enabled_indicators = []
|
enabled_indicators = []
|
||||||
|
|
@ -159,13 +162,13 @@ def settings():
|
||||||
if request.form[i] == 'indicator':
|
if request.form[i] == 'indicator':
|
||||||
enabled_indicators.append(i)
|
enabled_indicators.append(i)
|
||||||
# Set visibility for all indicators according to <enabled_indicators>
|
# Set visibility for all indicators according to <enabled_indicators>
|
||||||
for indctr in bt.app_data.indicators.indicator_list:
|
for indctr in app_data.indicators.indicator_list:
|
||||||
if indctr in enabled_indicators:
|
if indctr in enabled_indicators:
|
||||||
bt.app_data.indicators.indicator_list[indctr]['visible'] = True
|
app_data.indicators.indicator_list[indctr]['visible'] = True
|
||||||
else:
|
else:
|
||||||
bt.app_data.indicators.indicator_list[indctr]['visible'] = False
|
app_data.indicators.indicator_list[indctr]['visible'] = False
|
||||||
# Redirect without reloading history
|
# Redirect without reloading history
|
||||||
bt.app_data.config.config_and_states('save')
|
app_data.config.config_and_states('save')
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
|
||||||
elif setting == 'edit_indicator':
|
elif setting == 'edit_indicator':
|
||||||
|
|
@ -182,14 +185,14 @@ def settings():
|
||||||
if 'visible' not in attributes:
|
if 'visible' not in attributes:
|
||||||
attributes.update({'visible': False})
|
attributes.update({'visible': False})
|
||||||
# Set the data in indicators according to <attributes>
|
# Set the data in indicators according to <attributes>
|
||||||
bt.app_data.indicators.indicator_list[indicator] = attributes
|
app_data.indicators.indicator_list[indicator] = attributes
|
||||||
|
|
||||||
if 'delete' in request.form:
|
if 'delete' in request.form:
|
||||||
indicator = request.form['delete']
|
indicator = request.form['delete']
|
||||||
# This will delete in both indicators and config.
|
# This will delete in both indicators and config.
|
||||||
bt.app_data.indicators.delete_indicator(indicator)
|
app_data.indicators.delete_indicator(indicator)
|
||||||
# Redirect without reloading history
|
# Redirect without reloading history
|
||||||
bt.app_data.config.config_and_states('save')
|
app_data.config.config_and_states('save')
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
|
||||||
elif setting == 'new_indicator':
|
elif setting == 'new_indicator':
|
||||||
|
|
@ -210,35 +213,35 @@ def settings():
|
||||||
value = int(value)
|
value = int(value)
|
||||||
properties[key] = value
|
properties[key] = value
|
||||||
# Should create in indicators and update the list in config.
|
# Should create in indicators and update the list in config.
|
||||||
bt.app_data.indicators.create_indicator(name=indcr, itype=indtyp, properties=properties)
|
app_data.indicators.create_indicator(name=indcr, itype=indtyp, properties=properties)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('ERROR SETTING VALUE')
|
print('ERROR SETTING VALUE')
|
||||||
print(f'The string received by the server was: /n{request.form}')
|
print(f'The string received by the server was: /n{request.form}')
|
||||||
bt.app_data.config.config_and_states('save')
|
app_data.config.config_and_states('save')
|
||||||
bt.app_data.candles.set_candle_history()
|
app_data.candles.set_candle_history()
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/history')
|
@app.route('/history')
|
||||||
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
||||||
def history():
|
def history():
|
||||||
symbol = bt.app_data.config.trading_pair
|
symbol = app_data.config.trading_pair
|
||||||
interval = bt.app_data.config.chart_interval
|
interval = app_data.config.chart_interval
|
||||||
return jsonify(bt.app_data.candles.get_candle_history(symbol, interval, 1000))
|
return jsonify(app_data.candles.get_candle_history(symbol, interval, 1000))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/saved_data')
|
@app.route('/saved_data')
|
||||||
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
||||||
def saved_data():
|
def saved_data():
|
||||||
return jsonify(bt.app_data.indicators.indicator_list)
|
return jsonify(app_data.indicators.indicator_list)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/indicator_init')
|
@app.route('/indicator_init')
|
||||||
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
@cross_origin(origin='localhost', headers=['Content- Type', 'Authorization'])
|
||||||
def indicator_init():
|
def indicator_init():
|
||||||
symbol = bt.app_data.config.trading_pair
|
symbol = app_data.config.trading_pair
|
||||||
interval = bt.app_data.config.chart_interval
|
interval = app_data.config.chart_interval
|
||||||
d = bt.app_data.indicators.get_indicator_data(symbol, interval, 800)
|
d = app_data.indicators.get_indicator_data(symbol, interval, 800)
|
||||||
return jsonify(d)
|
return jsonify(d)
|
||||||
|
|
|
||||||
22
data.py
22
data.py
|
|
@ -7,6 +7,7 @@ from Configuration import Configuration
|
||||||
from exchange_info import ExchangeInfo
|
from exchange_info import ExchangeInfo
|
||||||
from indicators import Indicators
|
from indicators import Indicators
|
||||||
from Signals import Signals
|
from Signals import Signals
|
||||||
|
from trade import Trades
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -38,7 +39,10 @@ class BrighterData:
|
||||||
self.exchange_info = ExchangeInfo(self.client)
|
self.exchange_info = ExchangeInfo(self.client)
|
||||||
|
|
||||||
# Object that maintains the strategies data
|
# Object that maintains the strategies data
|
||||||
self.strategies = Strategies(self.config.strategies_list)
|
self.trades = Trades(self.client)
|
||||||
|
|
||||||
|
# Object that maintains the strategies data
|
||||||
|
self.strategies = Strategies(self.config.strategies_list, self.trades)
|
||||||
|
|
||||||
def get_js_init_data(self):
|
def get_js_init_data(self):
|
||||||
"""Returns a JSON object of initialization data
|
"""Returns a JSON object of initialization data
|
||||||
|
|
@ -80,10 +84,24 @@ class BrighterData:
|
||||||
i_updates = self.indicators.update_indicators()
|
i_updates = self.indicators.update_indicators()
|
||||||
# Process the signals based on the last indicator updates.
|
# Process the signals based on the last indicator updates.
|
||||||
state_changes = self.signals.process_all_signals(self.indicators)
|
state_changes = self.signals.process_all_signals(self.indicators)
|
||||||
|
# Update the trades instance.
|
||||||
|
trade_updates = self.trades.update(cdata)
|
||||||
|
# Update the strategies instance.
|
||||||
|
stg_updates = self.strategies.update(self.signals)
|
||||||
|
|
||||||
|
# Format and return an update object.
|
||||||
updates = {'i_updates': i_updates}
|
updates = {'i_updates': i_updates}
|
||||||
if state_changes:
|
if state_changes:
|
||||||
print(state_changes)
|
print(state_changes)
|
||||||
updates.update({'s_updates': state_changes})
|
updates.update({'s_updates': state_changes})
|
||||||
|
if stg_updates:
|
||||||
|
print(stg_updates)
|
||||||
|
updates.update({'stg_updts': stg_updates})
|
||||||
|
|
||||||
|
if trade_updates:
|
||||||
|
print(trade_updates)
|
||||||
|
updates.update({'trade_updts': trade_updates})
|
||||||
|
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
def received_new_signal(self, data):
|
def received_new_signal(self, data):
|
||||||
|
|
@ -136,5 +154,3 @@ class BrighterData:
|
||||||
# Delete the signal from the configuration file.
|
# Delete the signal from the configuration file.
|
||||||
self.config.remove('signals', signal_name)
|
self.config.remove('signals', signal_name)
|
||||||
|
|
||||||
|
|
||||||
app_data = BrighterData()
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@ class Alert{
|
||||||
// Other info in the alert.
|
// Other info in the alert.
|
||||||
this.state = state;
|
this.state = state;
|
||||||
// The alert messages.
|
// The alert messages.
|
||||||
this.msg = 'Signal state change: ' + this.source + ' = ' + this.state;
|
if (alert_type=='signal'){
|
||||||
|
this.msg = 'Signal state change: ' + this.source + ' = ' + this.state;
|
||||||
|
}
|
||||||
|
if (alert_type=='strategy'){
|
||||||
|
this.msg = 'Strategy alert: ' + this.source + ' = ' + this.state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
alert_source(){
|
alert_source(){
|
||||||
return this.source;
|
return this.source;
|
||||||
|
|
@ -44,6 +49,14 @@ class Alerts {
|
||||||
this.update_html();
|
this.update_html();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if (alert_type == 'strategy'){
|
||||||
|
// If the alert_type is strategy then data will
|
||||||
|
// contain a list of objects with format: { name: str, state: bool }
|
||||||
|
console.log('publishing strategy alerts')
|
||||||
|
this.alerts.push( new Alert('strategy', 'source', data) );
|
||||||
|
this.update_html();
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_html(){
|
update_html(){
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,18 @@ class Strategies {
|
||||||
strat.type = document.getElementById('strat_type').value;
|
strat.type = document.getElementById('strat_type').value;
|
||||||
strat.side = document.getElementById('trade_in_side').value;
|
strat.side = document.getElementById('trade_in_side').value;
|
||||||
strat.margin = document.getElementById('margin_select').value;
|
strat.margin = document.getElementById('margin_select').value;
|
||||||
|
strat.trade_amount = document.getElementById('trade_amount').value;
|
||||||
|
strat.max_position = document.getElementById('strgy_total').value;
|
||||||
|
strat.trading_fee = document.getElementById('fee').value;
|
||||||
|
strat.max_loss = document.getElementById('max_loss').value;
|
||||||
|
strat.symbol = window.UI.data.trading_pair;
|
||||||
|
strat.net_profit = 0;
|
||||||
|
strat.gross_profit = 0;
|
||||||
|
strat.net_loss = 0;
|
||||||
|
strat.gross_loss = 0;
|
||||||
|
strat.current_position = 0;
|
||||||
|
strat.current_value = 0;
|
||||||
|
strat.active = false;
|
||||||
strat.trd_in_conds = {};
|
strat.trd_in_conds = {};
|
||||||
let conds = Array.from(document.querySelectorAll('#trade_in_cond>li'));
|
let conds = Array.from(document.querySelectorAll('#trade_in_cond>li'));
|
||||||
for (let cond of conds){
|
for (let cond of conds){
|
||||||
|
|
@ -93,6 +105,15 @@ class Strategies {
|
||||||
window.UI.data.comms.send_to_app( "new_strategy", strat);
|
window.UI.data.comms.send_to_app( "new_strategy", strat);
|
||||||
this.close_form();
|
this.close_form();
|
||||||
}
|
}
|
||||||
|
update_received(stg_updts){
|
||||||
|
if ( 'cmd' in stg_updts) {
|
||||||
|
let alert =
|
||||||
|
window.UI.alerts.publish_alerts('strategy', stg_updts);
|
||||||
|
this.executeCmd(stg_updts.cmd);
|
||||||
|
}
|
||||||
|
console.log('recieved stategy update.');
|
||||||
|
console.log(stg_updts);
|
||||||
|
}
|
||||||
set_data(strats){
|
set_data(strats){
|
||||||
for (let strat of strats){
|
for (let strat of strats){
|
||||||
// Add the strategy to the instance list.
|
// Add the strategy to the instance list.
|
||||||
|
|
@ -128,7 +149,7 @@ class Strategies {
|
||||||
parent.appendChild(lbl);
|
parent.appendChild(lbl);
|
||||||
parent.appendChild(select);
|
parent.appendChild(select);
|
||||||
}
|
}
|
||||||
val_input(name, label, parent, min, max, i_val){
|
val_input(name, label, parent, min, max, i_val, style = null){
|
||||||
/* Create an input element.
|
/* Create an input element.
|
||||||
name: name and id of the element.
|
name: name and id of the element.
|
||||||
label: text displayed beside the element.
|
label: text displayed beside the element.
|
||||||
|
|
@ -144,8 +165,11 @@ class Strategies {
|
||||||
input.id = name;
|
input.id = name;
|
||||||
input.type = 'number';
|
input.type = 'number';
|
||||||
input.min = min;
|
input.min = min;
|
||||||
input.max = max;
|
if (max) {input.max = max;}
|
||||||
input.value = i_val;
|
input.value = i_val;
|
||||||
|
if (style){
|
||||||
|
input.style = style;
|
||||||
|
}
|
||||||
parent.appendChild(lbl);
|
parent.appendChild(lbl);
|
||||||
parent.appendChild(input);
|
parent.appendChild(input);
|
||||||
|
|
||||||
|
|
@ -170,12 +194,30 @@ class Strategies {
|
||||||
// Clear previous content.
|
// Clear previous content.
|
||||||
this.clear_innerHTML(options);
|
this.clear_innerHTML(options);
|
||||||
|
|
||||||
|
// Add a horizontal rule.
|
||||||
|
options.appendChild(document.createElement('hr'));
|
||||||
|
|
||||||
//Create a drop down for buy/sell option
|
//Create a drop down for buy/sell option
|
||||||
this.dropDown('trade_in_side','Side:', options, ['buy','sell']);
|
this.dropDown('trade_in_side','Side:', options, ['buy','sell']);
|
||||||
|
|
||||||
//Create an input for margin trading value. (1-100)
|
//Create an input for the margin. (1-100)
|
||||||
this.val_input('margin_select', 'Margin:', options, 1,100, 50);
|
this.val_input('margin_select', 'Margin:', options, 1,100, 50);
|
||||||
|
|
||||||
|
// Add a line break.
|
||||||
|
options.appendChild( document.createElement('br') );
|
||||||
|
|
||||||
|
//Create an input for the amount. (1-*)
|
||||||
|
this.val_input('trade_amount', 'Trade amount:', options, 1, 0, 1,'width: 54px;');
|
||||||
|
|
||||||
|
//Create an input for the amount. (1-*)
|
||||||
|
this.val_input('strgy_total', 'Strategy total:', options, 1, 0, 10, 'width: 54px;');
|
||||||
|
|
||||||
|
// Add a line break.
|
||||||
|
options.appendChild( document.createElement('br') );
|
||||||
|
|
||||||
|
//Create an input for the fee. (0.01-1)
|
||||||
|
this.val_input('fee', 'Trading Fee:', options, 0.001, 1, 0.025);
|
||||||
|
|
||||||
// Create a un ordered list to hold the conditions of trading in.
|
// Create a un ordered list to hold the conditions of trading in.
|
||||||
let ul_in_cond = document.createElement('ul');
|
let ul_in_cond = document.createElement('ul');
|
||||||
ul_in_cond.id ='trade_in_cond';
|
ul_in_cond.id ='trade_in_cond';
|
||||||
|
|
@ -251,6 +293,11 @@ class Strategies {
|
||||||
// Add a horizontal rule.
|
// Add a horizontal rule.
|
||||||
options.appendChild(document.createElement('hr'));
|
options.appendChild(document.createElement('hr'));
|
||||||
|
|
||||||
|
// Create an input for tolerable loss.
|
||||||
|
this.val_input('max_loss', 'Max loss :', options, 1,100, 50);
|
||||||
|
// Add a line break.
|
||||||
|
options.appendChild(document.createElement('br'));
|
||||||
|
|
||||||
//Create a drop down for Stop Loss type.
|
//Create a drop down for Stop Loss type.
|
||||||
this.dropDown('loss_typ','Stop-Loss type:', options, ['value', 'conditional']);
|
this.dropDown('loss_typ','Stop-Loss type:', options, ['value', 'conditional']);
|
||||||
// Add and onchange function to show and hide some options.
|
// Add and onchange function to show and hide some options.
|
||||||
|
|
@ -268,7 +315,7 @@ class Strategies {
|
||||||
// Add a line break.
|
// Add a line break.
|
||||||
options.appendChild( document.createElement('br') );
|
options.appendChild( document.createElement('br') );
|
||||||
|
|
||||||
// Create an input for take profit value.
|
// Create an input for stop loss value.
|
||||||
this.val_input('loss_val', 'Loss %:', options, 1,100, 50);
|
this.val_input('loss_val', 'Loss %:', options, 1,100, 50);
|
||||||
// Set display to visible.
|
// Set display to visible.
|
||||||
this.hideShowTags(['loss_val_lbl','loss_val'], 'show');
|
this.hideShowTags(['loss_val_lbl','loss_val'], 'show');
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,13 @@ class Comms {
|
||||||
window.UI.signals.update_signal_states(updates);
|
window.UI.signals.update_signal_states(updates);
|
||||||
window.UI.alerts.publish_alerts('signal_changes', updates);
|
window.UI.alerts.publish_alerts('signal_changes', updates);
|
||||||
}
|
}
|
||||||
|
if ('stg_updts' in msg.data){
|
||||||
|
/* If the received message contains signal updates.
|
||||||
|
Forward them to the signals instance. These messages
|
||||||
|
only arrive if a signal state changes. */
|
||||||
|
let stg_updts = msg.data['stg_updts'];
|
||||||
|
window.UI.strats.update_received(stg_updts);
|
||||||
|
}
|
||||||
}else if (msg.reply == 'signals'){
|
}else if (msg.reply == 'signals'){
|
||||||
/* On initialization the server will send a list of signals loaded from file.
|
/* On initialization the server will send a list of signals loaded from file.
|
||||||
Forward this to the signals instance.*/
|
Forward this to the signals instance.*/
|
||||||
|
|
|
||||||
205
trade.py
205
trade.py
|
|
@ -1,9 +1,200 @@
|
||||||
import data as bt_data
|
import json
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
def order(symbol, side, type, quantity):
|
class Trade:
|
||||||
try:
|
def __init__(self, symbol, side, order_type, position_size, price):
|
||||||
order = bt_data.client.create_order(symbol, side, type, quantity)
|
"""
|
||||||
# Report error if order fails
|
:param symbol: The symbol of the trading pair.
|
||||||
except Exception as e:
|
:param side: BUY|SELL
|
||||||
flash(e.message, "error")
|
:param order_type: LIMIT|MARKET
|
||||||
|
:param position_size: Value in USD
|
||||||
|
:param price: Asking price for LIMIT orders.
|
||||||
|
"""
|
||||||
|
# Returns a datetime object containing the local date and time
|
||||||
|
self.timestamp = datetime.now()
|
||||||
|
self.symbol = symbol
|
||||||
|
self.side = side
|
||||||
|
self.order_type = order_type
|
||||||
|
self.position_size = position_size
|
||||||
|
# Flag set once order is placed successfully.
|
||||||
|
self.order_placed = False
|
||||||
|
# Order info returned when order is placed.
|
||||||
|
self.order = None
|
||||||
|
# Flag set once order is filled
|
||||||
|
self.order_filled = False
|
||||||
|
# This flag is set when the trade is closed out.
|
||||||
|
self.trade_closed = False
|
||||||
|
# Opening value of the asset.
|
||||||
|
self.opening_price = price
|
||||||
|
# Current value
|
||||||
|
self.value = 0
|
||||||
|
# Profit or Loss
|
||||||
|
self.profit_loss = 0
|
||||||
|
# Profit or Loss in percentage.
|
||||||
|
self.pl_percentage = 0
|
||||||
|
|
||||||
|
def update(self, current_price):
|
||||||
|
# Utility function.
|
||||||
|
def percent(part, whole):
|
||||||
|
if whole == 0:
|
||||||
|
return 0
|
||||||
|
pct = 100 * float(part) / float(whole)
|
||||||
|
return pct
|
||||||
|
|
||||||
|
# Set the current value and profit/loss
|
||||||
|
initial_value = self.position_size * self.opening_price
|
||||||
|
self.value = self.position_size * current_price
|
||||||
|
if self.side == 'buy':
|
||||||
|
self.profit_loss = self.value - initial_value
|
||||||
|
else:
|
||||||
|
self.profit_loss = initial_value - self.value
|
||||||
|
self.pl_percentage = percent(self.profit_loss, initial_value)
|
||||||
|
|
||||||
|
return self.profit_loss
|
||||||
|
|
||||||
|
|
||||||
|
class Trades:
|
||||||
|
def __init__(self, client):
|
||||||
|
"""
|
||||||
|
:param client: The socket connection to the exchange.
|
||||||
|
"""
|
||||||
|
# Socket connection to the exchange.
|
||||||
|
self.client = client
|
||||||
|
# For automating limit orders offset the current price by 1/100 percent.
|
||||||
|
self.offset_amount = 0.0001
|
||||||
|
# Exchange fees. Maybe these can be fetched from the server?
|
||||||
|
self.exchange_fees = {'maker': 0.01, 'taker': 0.05}
|
||||||
|
# Hedge mode allows long and shorts to be placed simultaneously.
|
||||||
|
self.hedge_mode = False
|
||||||
|
# If hedge mode is disabled this is either {'buy','sell'}.
|
||||||
|
self.side = None
|
||||||
|
# A list of unfilled trades.
|
||||||
|
self.unfilled_trades = []
|
||||||
|
# A list of filled trades.
|
||||||
|
self.filled_trades = []
|
||||||
|
# A completed trades.
|
||||||
|
self.closed_trades = []
|
||||||
|
# Number of open trades.
|
||||||
|
self.num_trades = 0
|
||||||
|
# The quantity sum of all open trades.
|
||||||
|
self.total_position = 0
|
||||||
|
# The value of all open trades in USD.
|
||||||
|
self.total_position_value = 0
|
||||||
|
# Info on all futures symbols
|
||||||
|
self.info = client.futures_exchange_info()
|
||||||
|
# Dictionary of places the exchange requires after the decimal for all symbols.
|
||||||
|
self.symbols_n_precision = {}
|
||||||
|
for item in self.info['symbols']:
|
||||||
|
self.symbols_n_precision[item['symbol']] = item['quantityPrecision']
|
||||||
|
|
||||||
|
def update(self, cdata):
|
||||||
|
r_update = []
|
||||||
|
# Check if any unfilled orders are now filled.
|
||||||
|
for trade in self.unfilled_trades:
|
||||||
|
if not trade.order_filled:
|
||||||
|
order = self.client.get_order(symbol=trade.symbol, orderId=trade.order['orderId'])
|
||||||
|
if order['status'] == 'FILLED':
|
||||||
|
trade.order_filled = True
|
||||||
|
self.filled_trades.append(trade)
|
||||||
|
r_update.append({trade.timestamp: 'filled'})
|
||||||
|
# Delete filled trades from unfilled_trades
|
||||||
|
self.unfilled_trades[:] = (t for t in self.unfilled_trades if t.order_filled is False)
|
||||||
|
|
||||||
|
for trade in self.filled_trades:
|
||||||
|
# Update the open trades.
|
||||||
|
ret = trade.update(cdata['close'])
|
||||||
|
r_update.append({trade.timestamp: {'pl': ret}})
|
||||||
|
# If a trade has been closed...
|
||||||
|
if trade.trade_closed:
|
||||||
|
self.closed_trades.append(trade)
|
||||||
|
# Notify caller
|
||||||
|
r_update.append({trade.timestamp: 'closed'})
|
||||||
|
# Delete closed trades from filled_trades
|
||||||
|
self.filled_trades[:] = (t for t in self.filled_trades if t.trade_closed is False)
|
||||||
|
|
||||||
|
return r_update
|
||||||
|
|
||||||
|
def new_trade(self, symbol, side, order_type, usd, price=None):
|
||||||
|
# If hedge mode is disabled return False if trade is on opposite side.
|
||||||
|
if self.hedge_mode is False:
|
||||||
|
if self.side is None:
|
||||||
|
self.side = side
|
||||||
|
if self.side != side:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If no price is given, set the asking price to be offset by a small amount.
|
||||||
|
if price is None:
|
||||||
|
offset = self.get_price(symbol) * self.offset_amount
|
||||||
|
if side == 'buy':
|
||||||
|
price = f'-{offset}'
|
||||||
|
else:
|
||||||
|
price = f'+{offset}'
|
||||||
|
|
||||||
|
# A relative limit order may be set by passing a string preceded with a +/-
|
||||||
|
if type(price) == str:
|
||||||
|
if ('+' in price) or ('-' in price):
|
||||||
|
price = self.get_price(symbol) + float(price)
|
||||||
|
else:
|
||||||
|
price = float(price)
|
||||||
|
|
||||||
|
position_size = usd / price
|
||||||
|
# The required level of precision for this trading pair.
|
||||||
|
precision = self.symbols_n_precision[symbol]
|
||||||
|
# String representing the order amount formatted to the level of precision defined above.
|
||||||
|
order_amount = "{:0.0{}f}".format(position_size, precision)
|
||||||
|
# Create a trade and place the order.
|
||||||
|
trade = Trade(symbol, side, order_type, order_amount, price)
|
||||||
|
order = self.place_order(trade)
|
||||||
|
# If the order successfully placed store the trade.
|
||||||
|
if order:
|
||||||
|
# Set th order placed flag.
|
||||||
|
trade.order_placed = True
|
||||||
|
# Save the order info in the trade object.
|
||||||
|
trade.order = order
|
||||||
|
|
||||||
|
# Update the trades instance.
|
||||||
|
self.num_trades += 1
|
||||||
|
self.unfilled_trades.append(trade)
|
||||||
|
print(order)
|
||||||
|
return trade
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_price(symbol):
|
||||||
|
# Todo: Make sure im getting the correct market price for futures.
|
||||||
|
request = requests.get(f'https://api.binance.com/api/v3/ticker/price?symbol={symbol}')
|
||||||
|
json_obj = json.loads(request.text)
|
||||||
|
return json_obj['price']
|
||||||
|
|
||||||
|
def place_order(self, trade):
|
||||||
|
if trade.order_type == 'MARKET':
|
||||||
|
try:
|
||||||
|
order = self.client.create_test_order(symbol=trade.symbol,
|
||||||
|
side=trade.side,
|
||||||
|
type=trade.order_type,
|
||||||
|
quantity=trade.position_size)
|
||||||
|
print('!!!Order created!!!')
|
||||||
|
return order
|
||||||
|
# Report error if order fails
|
||||||
|
except Exception as e:
|
||||||
|
print(e, "error")
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif trade.order_type == 'LIMIT':
|
||||||
|
try:
|
||||||
|
order = self.client.create_test_order(
|
||||||
|
symbol=trade.symbol, side=trade.side, type=trade.order_type,
|
||||||
|
timeInForce='GTC', quantity=trade.position_size,
|
||||||
|
price=trade.opening_price)
|
||||||
|
return order
|
||||||
|
# If order fails
|
||||||
|
except Exception as e:
|
||||||
|
print(e, "error")
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f'Trade: No Implementation for trade.order: {trade.order_type}')
|
||||||
|
return None
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue