from Strategies import Strategies from candles import Candles from Configuration import Configuration from exchange import Exchange from indicators import Indicators from Signals import Signals from trade import Trades import json class BrighterData: def __init__(self): # Object that interacts and maintains exchange and account data self.exchange = Exchange() # Configuration and settings for the user interface and charts self.config = Configuration() # Object that maintains signals. Initialize with any signals loaded from file. self.signals = Signals(self.config.signals_list) # Object that maintains candlestick and price data. self.candles = Candles(self.config, self.exchange) # Object that interacts with and maintains data from available indicators self.indicators = Indicators(self.candles, self.config) # Object that maintains the trades data self.trades = Trades(self.config.trades) # The Trades object needs to connect to an exchange. self.trades.connect_exchange(exchange=self.exchange) # Object that maintains the strategies data self.strategies = Strategies(self.config.strategies_list, self.trades) def get_js_init_data(self): """ Returns a JSON object of initialization data. This is passed into the frontend HTML template for the javascript to access in the rendered HTML. """ js_data = {'i_types': self.indicators.indicator_types, 'indicators': self.indicators.indicator_list, 'interval': self.config.chart_interval, 'trading_pair': self.config.trading_pair} return js_data def get_rendered_data(self): """ Data to be injected into the HTML template that renders the frontend UI. """ rd = {} rd['title'] = self.config.application_title # Title of the page rd['my_balances'] = self.exchange.balances # Balances on the exchange rd['symbols'] = self.exchange.symbols # Symbols information from the exchange rd['intervals'] = self.exchange.intervals # Time candle time intervals available to stream rd['chart_interval'] = self.config.chart_interval # The charts current interval setting rd['indicator_types'] = self.indicators.indicator_types # All the types indicators Available rd['indicator_list'] = self.indicators.get_indicator_list() # indicators available rd['enabled_indicators'] = self.indicators.get_enabled_indicators() # list of indicators that are enabled rd['ma_vals'] = self.indicators.bb_ma_val # A list of acceptable values to use with bolenger band creation return rd def received_cdata(self, cdata): """ This is called to pass in new price data when it is received. :param cdata: - An object containing the most recent price data. :return: - Dictionary object containing information about the updates to be passed onto the UI. """ # If this is the first candle received last_candle will be empty. if not self.candles.last_candle: # Set last_candle. self.candles.last_candle = cdata # If this is not the first candle, but it's the same as the last candle recorded. elif cdata['time'] == self.candles.last_candle['time']: # Return without doing anything. return None # A new candle is received with a different timestamp from the last candle. # Update the instance data records. self.candles.set_new_candle(cdata) # Update the indicators and receive a dictionary of indicator results. i_updates = self.indicators.update_indicators() # Now that all the indicators have changed. Process the signals and receive a list of signals that # have changed their states. state_changes = self.signals.process_all_signals(self.indicators) # Update the trades instance with the new price data. trade_updates = self.trades.update(cdata) # Update the strategies instance. Strategy execution is based on the signal states and trade values. # This must be updated last. stg_updates = self.strategies.update(self.signals) # Format and return an update object to pass information to the frontend UI. updates = {} if i_updates: updates.update({'i_updates': i_updates}) if state_changes: print(state_changes) updates.update({'s_updates': state_changes}) if trade_updates: print(trade_updates) updates.update({'trade_updts': trade_updates}) if stg_updates: print(stg_updates) updates.update({'stg_updts': stg_updates}) return updates def received_new_signal(self, data): """ This is called when a new Signal has been defined and created in the UI. :param data: - The attributes of the signal. :return: An error if failed. On success return incoming data for chaining. """ # Validate the incoming data. if 'name' not in data: return 'data.py:received_new_signal() - The new signal has no name. ' # Forward the new signal data to the signals instance. So it can create a new signal. self.signals.new_signal(data) # Update config's list of signals and save it to file. self.config.update_data('signals', self.signals.get_signals('dict')) # Send the data back to where it came from. return data def received_new_strategy(self, data): """ This is called when a new Strategy has been defined and created in the UI. :param data: - The attributes of the strategy. :return: An error if failed. On success return incoming data for chaining. """ # Validate the incoming data. if 'name' not in data: return 'data.py:received_new_strategy() - The new strategy has no name. ' # Forward the new strategy data to the strategy's instance. So it can create a new strategy. self.strategies.new_strategy(data) # Update config's list of strategies and save to file. self.config.update_data('strategies', self.strategies.get_strategies('dict')) # Send the data back to where it came from. return data def delete_strategy(self, strategy_name): # Delete the strategy from the strategies instance. self.strategies.delete_strategy(strategy_name) # Delete the strategy from the configuration file. self.config.remove('strategies', strategy_name) def get_signals(self): """Return a JSON object of all the signals in the signals instance.""" return self.signals.get_signals('json') def get_strategies(self): """ Return a JSON object of all the strategies in the strategies instance.""" return self.strategies.get_strategies('json') def delete_signal(self, signal_name): # Delete the signal from the signals instance. self.signals.delete_signal(signal_name) # Delete the signal from the configuration file. self.config.remove('signals', signal_name) def received_new_trade(self, data): """ This is called when a new trade has been defined and created in the UI. Todo: Note - I handled this differently then signals and strategies. Is this better or not? :param data: - The attributes of the trade. :return: An error if failed. On success return incoming data to forward back to the UI. """ def vld(attr): # Verify and validate input data. if attr in data and data[attr] != '': try: return float(data[attr]) except ValueError: return data[attr] else: return None # Forward the request to trades. status, result = self.trades.new_trade(target=vld('target'), symbol=vld('symbol'), price=vld('price'), side=vld('side'), order_type=vld('orderType'), qty=vld('quantity')) # Log any error to the terminal. if status == 'Error': print(f'\napp.py:trade() - Error placing the trade: {result}') # Log order to terminal. print(f"\napp.py:trade() - Trade order received: target={vld('target')}, symbol={vld('symbol')}," f" side={vld('side')}, type={vld('orderType')}, quantity={vld('quantity')},price={vld('price')}") # Update config's list of trades and save to file. self.config.update_data('trades', self.trades.get_trades('dict')) # Send the data back to where it came from. return data # todo: This is how I handled this when I was using the flask interface to catch an html form submission. # # Set the default symbol to the trading pair the UI is currently focused on. # if symbol is None: # symbol = self.config.trading_pair # self.trades.new_trade(target=target, symbol=symbol, side=side, order_type=order_type, # qty=quantity, price=price, offset=None) # # Update config's list of trades and save to file. # self.config.update_data('trades', self.trades.get_trades('dict')) def get_trades(self): """ Return a JSON object of all the trades in the trades instance.""" return self.trades.get_trades('json')