219 lines
9.4 KiB
Python
219 lines
9.4 KiB
Python
import datetime
|
|
import csv
|
|
|
|
|
|
class Candles:
|
|
def __init__(self, config, client):
|
|
# Keep a reference to the exchange client.
|
|
self.client = client
|
|
# The maximum amount of data to load into memory at one time.
|
|
self.max_data_loaded = config.max_data_loaded
|
|
# A reference to the app 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):
|
|
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']})
|
|
|
|
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 <symbol> 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. <Open_time>,<Open>,<High>,<Low>,<Close>,
|
|
# <Ignore><Close_time><Ignore>
|
|
# <Number_of_bisic_data>,<Ignore,Ignore,Ignore>
|
|
candlesticks = []
|
|
|
|
try:
|
|
# Populate <candlesticks> from <file_name> 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 <file_name> 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 <file_name> 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 <last_candle_stamp> with a default value stored in <start_datetime>.
|
|
if not candlesticks:
|
|
last_candle_stamp = start_datetime.timestamp() * 1000
|
|
else:
|
|
# Set <last_candle_stamp> 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.client.get_historical_klines(symbol, interval, start_str=int(last_candle_stamp))
|
|
# 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
|