import datetime import csv from binance.enums import HistoricalKlinesType class Candles: def __init__(self, config, exchange): # Keep a reference to the exchange object. self.exchange = exchange # The maximum amount of data to load into memory at one time. self.max_data_loaded = config.max_data_loaded # A reference to the interface configuration self.config = config # The entire loaded candle history self.candlesticks = [] # List of dictionaries of timestamped high, low, and closing values self.latest_high_values = [] self.latest_low_values = [] self.latest_close_values = [] # Values of the last candle received self.last_candle = None # List of dictionaries of timestamped volume values self.latest_vol = [] # Set the instance variable of candlesticks, latest_close_values, high, low, closing, volume, and last_candle self.set_candle_history(symbol=config.trading_pair, interval=config.chart_interval) def set_new_candle(self, cdata): # TODO: this is not updating self.candlesticks[]. I think it is because it is # todo: not in the raw format received earlier. check that the format is the same. # todo: candlesticks is accessed by get_candle_history() while init the charts self.last_candle = cdata self.latest_close_values.append({'time': cdata['time'], 'close': cdata['close']}) self.latest_high_values.append({'time': cdata['time'], 'high': cdata['high']}) self.latest_low_values.append({'time': cdata['time'], 'low': cdata['low']}) self.latest_vol.append({'time': cdata['time'], 'value': cdata['vol']}) return True def load_candle_history(self, symbol, interval): """ Retrieve candlestick history from a file and append it with more recent exchange data while updating the file record. This method only get called if the data is requested. This is to avoid maintaining irrelevant data files.""" start_datetime = datetime.datetime(2017, 1, 1) # Create a filename from the function parameters. # Format is symbol_interval_start_date: example - 'BTCUSDT_15m_2017-01-01' file_name = 'data' + '/' + f'{symbol}_{interval}_{start_datetime.date()}' # List of price data. ,,,,, # # , candlesticks = [] try: # Populate from if it exists. print(f'Attempting to open: {file_name}') with open(file_name, 'r') as file: reader = csv.reader(file, delimiter=',') # Load the data here for row in reader: candlesticks.append(row) print('File loaded') # Open for appending file = open(file_name, 'a', newline='') candlestick_writer = csv.writer(file, delimiter=',') except IOError: # If the file doesn't exist it must be created. print(f'{file_name} not found: Creating the file') # Open for writing file = open(file_name, 'w', newline='') candlestick_writer = csv.writer(file, delimiter=',') # If no candlesticks were loaded from file. Set a date to start loading from in the # variable with a default value stored in . if not candlesticks: last_candle_stamp = start_datetime.timestamp() * 1000 else: # Set with the timestamp of the last candles on file last_candle_stamp = candlesticks[-1][0] # Request any missing candlestick data from the exchange recent_candlesticks = self.exchange.client.get_historical_klines(symbol, interval, start_str=int(last_candle_stamp), klines_type=HistoricalKlinesType.FUTURES) # Discard the first row of candlestick data as it will be a duplicate***DOUBLE CHECK THIS recent_candlesticks.pop(0) # Append the candlestick list and the file for candlestick in recent_candlesticks: candlesticks.append(candlestick) candlestick_writer.writerow(candlestick) # Close the file and return the entire candlestick history file.close() return candlesticks def set_candle_history(self, symbol=None, interval=None, max_data_loaded=None): if not max_data_loaded: max_data_loaded = self.max_data_loaded if not symbol: symbol = self.config.trading_pair print(f'set_candle_history(): No symbol provided. Using{symbol}') if not interval: interval = self.config.chart_interval print(f'set_candle_history(): No interval provided. Using{interval}') if self.candlesticks: print(f'set_candle_history(): Reloading {interval} candles.') else: print(f'set_candle_history(): Loading {interval} candles.') # Load candles from file cdata = self.load_candle_history(symbol, interval) # Trim the beginning of the returned list to size of max_data_loaded of less if len(cdata) < max_data_loaded: max_data_loaded = len(cdata) self.candlesticks = cdata[-max_data_loaded:] # Set an instance dictionary of timestamped high, low, closing values self.set_latest_values('high') self.set_latest_values('low') self.set_latest_values('close') # Extract the volume data from self.candlesticks and store it in self.latest_vol self.set_latest_vol() # Set an instance reference of the last candle self.last_candle = self.convert_candle(self.candlesticks[-1]) print('set_candle_history(): Candle data Loaded') return def set_latest_vol(self): # Extracts a list of volume values from all the loaded candlestick # data and store it in a dictionary keyed to timestamp of measurement. latest_vol = [] last_clp = 0 for data in self.candlesticks: clp = int(float(data[4])) if clp < last_clp: color = 'rgba(255,82,82, 0.8)' # red else: color = 'rgba(0, 150, 136, 0.8)' # green vol_data = { "time": int(data[0]) / 1000, "value": int(float(data[5])), "color": color } last_clp = clp latest_vol.append(vol_data) self.latest_vol = latest_vol return def get_latest_vol(self, num_record=500): # Returns the latest volume values if self.latest_vol: if len(self.latest_vol) < num_record: print('Warning: get_latest_vol() - Requested too more records then available') num_record = len(self.latest_vol) return self.latest_vol[-num_record:] else: raise ValueError('Warning: get_latest_vol(): Values are not set') def set_latest_values(self, value_name): # Extracts a list of values from all the loaded candlestick data # and store it in a dictionary keyed to timestamp of measurement. index = {'high': 2, 'low': 3, 'close': 4} latest_values = [] for data in self.candlesticks: data = { "time": int(data[0]) / 1000, value_name: data[index[value_name]] } latest_values.append(data) if value_name == 'high': self.latest_high_values = latest_values if value_name == 'low': self.latest_low_values = latest_values if value_name == 'close': self.latest_close_values = latest_values return def get_latest_values(self, value_name, num_record=1): # Returns the latest values values = None if value_name == 'high': values = self.latest_high_values if value_name == 'low': values = self.latest_low_values if value_name == 'close': values = self.latest_close_values if values is None: raise ValueError(f'Warning: latest_values(): "{value_name}" values are not set.') if len(values) < num_record: print('Warning: get_latest_values() - Requested more records then available') num_record = len(values) return values[-num_record:] @staticmethod def convert_candle(candle): # Converts the binance candle format to what lightweight charts expects. candlestick = { "time": int(candle[0]) / 1000, "open": candle[1], "high": candle[2], "low": candle[3], "close": candle[4] } return candlestick def get_candle_history(self, symbol, interval, num_records): # Returns a specified number of candle records from memory in the lightweight # charts format. if len(self.candlesticks) < num_records: print('Warning: get_candle_history() Requested more records then available') num_records = len(self.candlesticks) # Drop everything but the requested number of records candlesticks = self.candlesticks[-num_records:] # Reformat relevant candlestick data into a list of python dictionary objects. # Binance stores timestamps in milliseconds but lightweight charts doesn't, # so it gets divided by 1000 processed_candlesticks = [] for candle in candlesticks: candlestick = self.convert_candle(candle) processed_candlesticks.append(candlestick) # Return a list of candlestick objects return processed_candlesticks