brighter-trading/candles.py

258 lines
11 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 = 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_high_values()
self.set_latest_low_values()
self.set_latest_close_values()
# 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 closing 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_high_values(self):
# Extracts a list of close values from all the loaded candlestick
# data and store it in a dictionary keyed to timestamp of measurement.
latest_high_values = []
for data in self.candlesticks:
high_data = {
"time": int(data[0]) / 1000,
"high": data[2]
}
latest_high_values.append(high_data)
self.latest_high_values = latest_high_values
return
def get_latest_high_values(self, num_record=500):
# Returns the latest closing values
if self.latest_high_values:
if len(self.latest_high_values) < num_record:
print('Warning: latest_high_values() - Requested too more records then available')
num_record = len(self.latest_high_values)
return self.latest_high_values[-num_record:]
else:
raise ValueError('Warning: latest_high_values(): Values are not set')
def set_latest_low_values(self):
# Extracts a list of close values from all the loaded candlestick
# data and store it in a dictionary keyed to timestamp of measurement.
latest_low_values = []
for data in self.candlesticks:
low_data = {
"time": int(data[0]) / 1000,
"low": data[3]
}
latest_low_values.append(low_data)
self.latest_low_values = latest_low_values
return
def get_latest_low_values(self, num_record=500):
# Returns the latest closing values
if self.latest_low_values:
if len(self.latest_low_values) < num_record:
print('Warning: latest_low_values() - Requested too more records then available')
num_record = len(self.latest_low_values)
return self.latest_low_values[-num_record:]
else:
raise ValueError('Warning: latest_low_values(): Values are not set')
def set_latest_close_values(self):
# Extracts just the timestamped close values from all the loaded candlestick
# data and store it in the class instance as a dictionary.
latest_close_values = []
for data in self.candlesticks:
close_data = {
"time": int(data[0]) / 1000,
"close": data[4]
}
latest_close_values.append(close_data)
self.latest_close_values = latest_close_values
return
def get_latest_close_values(self, num_record=500):
# Returns the latest closing values from the class instance.
if self.latest_close_values:
if len(self.latest_close_values) < num_record:
print('Warning: get_latest_close_values() - Requested too more records then available')
num_record = len(self.latest_close_values)
return self.latest_close_values[-num_record:]
else:
raise ValueError('Warning: get_latest_close_values(): Values are not set')
@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
def get_volume(self, i_type, num_results=800):
r_data = self.get_latest_vol()
r_data = r_data[-num_results:]
return {"type": i_type, "data": r_data}