Classes implemented in python and javascript. UML class diagram. Rough sequence uml. TODO: local file getting dirty from refresh. Signals implemented. strategies half implemented.
This commit is contained in:
parent
de4686b109
commit
5f5f34c945
|
|
@ -1,6 +1,7 @@
|
|||
from binance.enums import *
|
||||
import yaml
|
||||
|
||||
from Signals import Signals
|
||||
from indicators import Indicators
|
||||
|
||||
|
||||
|
|
@ -24,16 +25,35 @@ class Configuration:
|
|||
# Call a static method from indicators that fills in a default list of indicators in config.
|
||||
self.indicator_list = Indicators.get_indicator_defaults()
|
||||
|
||||
# Call a static method from indicators that fills in a default list of indicators in config.
|
||||
self.signals_list = Signals.get_signals_defaults()
|
||||
|
||||
# The data that will be saved and loaded from file .
|
||||
self.saved_data = None
|
||||
|
||||
def new_signal(self, data):
|
||||
# Create a new signal.
|
||||
self.saved_data['signals'].append(data)
|
||||
# Save it to file.
|
||||
self.config_and_states('save')
|
||||
|
||||
def remove_signal(self, data):
|
||||
print(f'removing {data}')
|
||||
for sig in self.saved_data['signals']:
|
||||
if sig['name'] == data:
|
||||
self.saved_data['signals'].remove(sig)
|
||||
break
|
||||
# Save it to file.
|
||||
self.config_and_states('save')
|
||||
|
||||
def config_and_states(self, cmd):
|
||||
"""Loads or saves configurable data to the file set in self.config_FN"""
|
||||
|
||||
# The data stored and retrieved from file session.
|
||||
self.saved_data = {
|
||||
'indicator_list': self.indicator_list,
|
||||
'config': {'chart_interval': self.chart_interval, 'trading_pair': self.trading_pair}
|
||||
'config': {'chart_interval': self.chart_interval, 'trading_pair': self.trading_pair},
|
||||
'signals': self.signals_list
|
||||
}
|
||||
|
||||
def set_loaded_values():
|
||||
|
|
@ -41,6 +61,7 @@ class Configuration:
|
|||
self.indicator_list = self.saved_data['indicator_list']
|
||||
self.chart_interval = self.saved_data['config']['chart_interval']
|
||||
self.trading_pair = self.saved_data['config']['trading_pair']
|
||||
self.signals_list = self.saved_data['signals']
|
||||
|
||||
def load_configuration(filepath):
|
||||
"""load file data"""
|
||||
|
|
|
|||
37888
Sequence overview.mdj
37888
Sequence overview.mdj
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,153 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass()
|
||||
class Signal:
|
||||
"""Class for individual signal properties and state."""
|
||||
name: str
|
||||
source1: str
|
||||
prop1: str
|
||||
source2: str
|
||||
prop2: str
|
||||
operator: str
|
||||
state: bool = False
|
||||
value1: int = None
|
||||
value2: int = None
|
||||
range: int = None
|
||||
|
||||
def set_value1(self, value):
|
||||
self.value1 = value
|
||||
|
||||
def set_value2(self, value):
|
||||
self.value2 = value
|
||||
|
||||
def compare(self):
|
||||
|
||||
if self.value1 is None:
|
||||
raise ValueError('Signal: Cannot compare: value1 not set')
|
||||
if self.value2 is None:
|
||||
raise ValueError('Signal: Cannot compare: value2 not set')
|
||||
|
||||
previous_state = self.state
|
||||
|
||||
if self.operator == '+/-':
|
||||
if self.range is None:
|
||||
raise ValueError('Signal: Cannot compare: range not set')
|
||||
if abs(self.value1 - self.value2) < self.range:
|
||||
self.state = True
|
||||
else:
|
||||
self.state = False
|
||||
else:
|
||||
string = str(self.value1) + self.operator + str(self.value2)
|
||||
print(string)
|
||||
if eval(string):
|
||||
self.state = True
|
||||
else:
|
||||
self.state = False
|
||||
state_change = False
|
||||
if self.state != previous_state:
|
||||
state_change = True
|
||||
return state_change
|
||||
|
||||
|
||||
class Signals:
|
||||
def __init__(self):
|
||||
self.signals = []
|
||||
self.set_signals_defaults()
|
||||
|
||||
def set_signals_defaults(self):
|
||||
"""These defaults are loaded if the config file is not found."""
|
||||
sigs = self.get_signals_defaults()
|
||||
for sig in sigs:
|
||||
self.signals.append(Signal(name=sig['name'], source1=sig['source1'],
|
||||
prop1=sig['prop1'], operator=sig['operator'],
|
||||
source2=sig['source2'], prop2=sig['prop2'],
|
||||
state=sig['state']))
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def get_signals_defaults():
|
||||
"""These defaults are loaded if the config file is not found."""
|
||||
s1 = {"name": "20x50_GC", "source1": "EMA 20",
|
||||
"prop1": "value", "source2": "EMA 50",
|
||||
"prop2": "value", "operator": ">",
|
||||
"state": False, "value1": None,
|
||||
"value2": None, "range": None}
|
||||
s2 = {"name": "50x200_GC", "source1": "EMA 50",
|
||||
"prop1": "value", "source2": "EMA 200",
|
||||
"prop2": "value", "operator": ">",
|
||||
"state": False, "value1": None,
|
||||
"value2": None, "range": None}
|
||||
s3 = {"name": "5x15_GC", "source1": "EMA 5",
|
||||
"prop1": "value", "source2": "EMA 15",
|
||||
"prop2": "value", "operator": ">",
|
||||
"state": False, "value1": None,
|
||||
"value2": None, "range": None}
|
||||
return [s1, s2, s3]
|
||||
|
||||
def init_loaded_signals(self, signals_list):
|
||||
for sig in signals_list:
|
||||
self.signals.append(Signal(name=sig['name'], source1=sig['source1'],
|
||||
prop1=sig['prop1'], operator=sig['operator'],
|
||||
source2=sig['source2'], prop2=sig['prop2'],
|
||||
state=sig['state']))
|
||||
print(self.signals)
|
||||
|
||||
def get_signals(self):
|
||||
return self.signals
|
||||
|
||||
def new_signal(self, data):
|
||||
self.signals.append(Signal(**data))
|
||||
|
||||
def delete_signal(self, signal_name):
|
||||
print(f'removing {signal_name}')
|
||||
for sig in self.signals:
|
||||
if sig.name == signal_name:
|
||||
self.signals.remove(sig)
|
||||
break
|
||||
|
||||
def update_signals(self, candles, indicators):
|
||||
# TODO: This function is not used. but may be used later if candles need to be specified.
|
||||
for signal in self.signals:
|
||||
self.process_signal(signal, candles, indicators)
|
||||
|
||||
def process_all_signals(self, indicators):
|
||||
"""Loop through all the signals and process
|
||||
them based on the last indicator results."""
|
||||
state_changes = {}
|
||||
for signal in self.signals:
|
||||
change_in_state = self.process_signal(signal, indicators)
|
||||
if change_in_state:
|
||||
state_changes.update({signal.name: signal.state})
|
||||
return state_changes
|
||||
|
||||
def process_signal(self, signal, indicators, candles=None):
|
||||
"""Receives a signal, makes the comparison with the last value
|
||||
calculated by the indicators and returns the result.
|
||||
If candles are provided it will ask the indicators to
|
||||
calculate new values based on those candles."""
|
||||
if candles is None:
|
||||
# Get the source of the first signal
|
||||
source_1 = signal.source1
|
||||
# Ask the indicator for the last result.
|
||||
if source_1 in indicators.indicators:
|
||||
signal.value1 = indicators.indicators[source_1].properties[signal.prop1]
|
||||
else:
|
||||
print('Could not calculate signal source indicator not found.')
|
||||
return False
|
||||
|
||||
# Get the source of the second signal
|
||||
source_2 = signal.source2
|
||||
# If the source is a set value it will be stored in prop2
|
||||
if source_2 == 'value':
|
||||
signal.value2 = signal.prop2
|
||||
else:
|
||||
# Ask the indicator for the last result.
|
||||
if source_2 in indicators.indicators:
|
||||
signal.value2 = indicators.indicators[source_2].properties[signal.prop2]
|
||||
else:
|
||||
print('Could not calculate signal source2 indicator not found.')
|
||||
return False
|
||||
# Compare the retrieved values.
|
||||
state_change = signal.compare()
|
||||
return state_change
|
||||
34593
UML Overview.mdj
34593
UML Overview.mdj
File diff suppressed because it is too large
Load Diff
24
app.py
24
app.py
|
|
@ -46,14 +46,27 @@ def ws(sock):
|
|||
r_data = bt.app_data.received_cdata(msg_obj['data'])
|
||||
if r_data:
|
||||
resp = {
|
||||
"reply": "i_updates",
|
||||
"reply": "updates",
|
||||
"data": r_data
|
||||
}
|
||||
sock.send(json.dumps(resp))
|
||||
|
||||
if msg_obj['message_type'] == 'request':
|
||||
print(msg_obj['req'])
|
||||
print('Request!')
|
||||
if msg_obj['data'] == 'signals':
|
||||
signals = bt.app_data.get_signals()
|
||||
if signals:
|
||||
resp = {
|
||||
"reply": "signals",
|
||||
"data": signals
|
||||
}
|
||||
resp = json.dumps(resp)
|
||||
sock.send(resp)
|
||||
else:
|
||||
print('Warning: Unhandled request!')
|
||||
print(msg_obj['data'])
|
||||
|
||||
if msg_obj['message_type'] == 'delete_signal':
|
||||
bt.app_data.delete_signal(msg_obj['data'])
|
||||
|
||||
if msg_obj['message_type'] == 'reply':
|
||||
print(msg_obj['rep'])
|
||||
|
|
@ -65,10 +78,11 @@ def ws(sock):
|
|||
r_data = bt.app_data.received_new_signal(msg_obj['data'])
|
||||
if r_data:
|
||||
resp = {
|
||||
"reply": "i_updates",
|
||||
"reply": "signal_created",
|
||||
"data": r_data
|
||||
}
|
||||
sock.send(json.dumps(resp))
|
||||
resp = json.dumps(resp)
|
||||
sock.send(resp)
|
||||
return
|
||||
|
||||
# The rendered page connects to the exchange and relays the candle data back here
|
||||
|
|
|
|||
108
candles.py
108
candles.py
|
|
@ -41,7 +41,7 @@ class Candles:
|
|||
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()}'
|
||||
file_name = 'data' + '/' + f'{symbol}_{interval}_{start_datetime.date()}'
|
||||
|
||||
# List of price data. <Open_time>,<Open>,<High>,<Low>,<Close>,
|
||||
# <Ignore><Close_time><Ignore>
|
||||
|
|
@ -107,9 +107,9 @@ class Candles:
|
|||
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()
|
||||
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()
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ class Candles:
|
|||
return
|
||||
|
||||
def get_latest_vol(self, num_record=500):
|
||||
# Returns the latest closing values
|
||||
# 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')
|
||||
|
|
@ -150,75 +150,40 @@ class Candles:
|
|||
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 = []
|
||||
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:
|
||||
high_data = {
|
||||
data = {
|
||||
"time": int(data[0]) / 1000,
|
||||
"high": data[2]
|
||||
value_name: data[index[value_name]]
|
||||
}
|
||||
latest_high_values.append(high_data)
|
||||
self.latest_high_values = latest_high_values
|
||||
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_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(f'Warning: get_latest_close_values() - Requested {num_record} too more records then available')
|
||||
print(len(self.latest_close_values))
|
||||
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')
|
||||
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):
|
||||
|
|
@ -251,8 +216,3 @@ class Candles:
|
|||
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}
|
||||
|
|
|
|||
82
config.yml
82
config.yml
|
|
@ -1,82 +0,0 @@
|
|||
config:
|
||||
chart_interval: 1d
|
||||
trading_pair: BTCUSDT
|
||||
indicator_list:
|
||||
Bolenger:
|
||||
color_1: '#5ad858'
|
||||
color_2: '#f0f664'
|
||||
color_3: '#5ad858'
|
||||
devdn: 2
|
||||
devup: 2
|
||||
ma: 1
|
||||
period: 20
|
||||
type: BOLBands
|
||||
value: 0
|
||||
value1: '38691.58'
|
||||
value2: '38552.36'
|
||||
value3: '38413.14'
|
||||
visible: 'True'
|
||||
EMA 100:
|
||||
color: '#286006'
|
||||
period: 100
|
||||
type: EMA
|
||||
value: 0
|
||||
visible: true
|
||||
EMA 50:
|
||||
color: '#fe534c'
|
||||
period: 50
|
||||
type: EMA
|
||||
value: 0
|
||||
visible: true
|
||||
MACD:
|
||||
color_1: '#675c3b'
|
||||
color_2: '#54fcd6'
|
||||
fast_p: 12
|
||||
hist: 0
|
||||
macd: 0
|
||||
signal: 0
|
||||
signal_p: 9
|
||||
slow_p: 26
|
||||
type: MACD
|
||||
value: 0
|
||||
visible: true
|
||||
New rsi:
|
||||
color: '#1f8438'
|
||||
period: 10
|
||||
type: RSI
|
||||
value: '38.67'
|
||||
visible: 'True'
|
||||
RSI 14:
|
||||
color: '#07120c'
|
||||
period: 14
|
||||
type: RSI
|
||||
value: 0
|
||||
visible: true
|
||||
RSI 8:
|
||||
color: '#4c4fe9'
|
||||
period: 8
|
||||
type: RSI
|
||||
value: 0
|
||||
visible: true
|
||||
SMA 200:
|
||||
color: '#cdc178'
|
||||
period: 200
|
||||
type: SMA
|
||||
value: 0
|
||||
visible: true
|
||||
SMA 21:
|
||||
color: '#2847ce'
|
||||
period: 21
|
||||
type: SMA
|
||||
value: 0
|
||||
visible: true
|
||||
Testing1:
|
||||
color: '#ffa500'
|
||||
period: 20
|
||||
type: RSI
|
||||
value: 0
|
||||
visible: true
|
||||
vol:
|
||||
type: Volume
|
||||
value: 0
|
||||
visible: true
|
||||
39
data.py
39
data.py
|
|
@ -5,6 +5,8 @@ from candles import Candles
|
|||
from Configuration import Configuration
|
||||
from exchange_info import ExchangeInfo
|
||||
from indicators import Indicators
|
||||
from Signals import Signals
|
||||
import json
|
||||
|
||||
|
||||
class BrighterData:
|
||||
|
|
@ -13,12 +15,18 @@ class BrighterData:
|
|||
# Initialise a connection to the Binance client API
|
||||
self.client = Client(config.API_KEY, config.API_SECRET)
|
||||
|
||||
# Object that maintains signals
|
||||
self.signals = Signals()
|
||||
|
||||
# Configuration and settings for the user interface and charts
|
||||
self.config = Configuration()
|
||||
|
||||
# Load any saved data from file
|
||||
self.config.config_and_states('load')
|
||||
|
||||
# Initialize signals with loaded data.
|
||||
self.signals.init_loaded_signals(self.config.signals_list)
|
||||
|
||||
# Object that maintains candlestick and price data.
|
||||
self.candles = Candles(self.config, self.client)
|
||||
|
||||
|
|
@ -65,15 +73,38 @@ class BrighterData:
|
|||
|
||||
# New candle is received update the instance data records. And the indicators.
|
||||
self.candles.set_new_candle(cdata)
|
||||
updates = self.indicators.update_indicators()
|
||||
i_updates = self.indicators.update_indicators()
|
||||
# Process the signals based on the last indicator updates.
|
||||
state_changes = self.signals.process_all_signals(self.indicators)
|
||||
updates = {'i_updates': i_updates}
|
||||
if state_changes:
|
||||
print(state_changes)
|
||||
updates.update({'s_updates': state_changes})
|
||||
return updates
|
||||
|
||||
def received_new_signal(self, data):
|
||||
# Check the data.
|
||||
if 'sigName' not in data:
|
||||
if 'name' not in data:
|
||||
return 'data.py:received_new_signal() - The new signal has no name. '
|
||||
print(data)
|
||||
# TODO lets go!
|
||||
# Forward the new signal data to the signals instance. So it can create a new signal.
|
||||
self.signals.new_signal(data)
|
||||
# Forward the new signal data to config. So it can save it to file.
|
||||
self.config.new_signal(data)
|
||||
return data
|
||||
|
||||
def get_signals(self):
|
||||
""" Return a JSON object of all the signals in the signals instance."""
|
||||
sigs = self.signals.get_signals()
|
||||
json_str = []
|
||||
for sig in sigs:
|
||||
json_str.append(json.dumps(sig.__dict__))
|
||||
return json_str
|
||||
|
||||
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_signal(signal_name)
|
||||
|
||||
|
||||
app_data = BrighterData()
|
||||
|
|
|
|||
239
indicators.py
239
indicators.py
|
|
@ -2,6 +2,9 @@ import random
|
|||
import numpy as np
|
||||
import talib
|
||||
|
||||
# Create a List of all available indicator types
|
||||
indicator_types = []
|
||||
|
||||
|
||||
class Indicator:
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
|
|
@ -14,13 +17,75 @@ class Indicator:
|
|||
if 'visible' not in properties:
|
||||
self.properties['visible'] = True
|
||||
|
||||
def get_records(self, value_name, candles, num_results=1):
|
||||
# These indicators do computations over a period of price data points.
|
||||
# We need to give it at least that amount of data plus the results requested.
|
||||
num_records = self.properties['period'] + num_results
|
||||
data = candles.get_latest_values(value_name, num_records)
|
||||
if len(data) < num_records:
|
||||
print(f'Could not calculate {self.properties["type"]} for time period of {self.properties["period"]}')
|
||||
print('Not enough data available.')
|
||||
return
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def isolate_values(value_name, data):
|
||||
# Isolate the values and timestamps from the dictionary object.
|
||||
values = []
|
||||
timestamps = []
|
||||
for each in data:
|
||||
values.append(each[value_name])
|
||||
timestamps.append(each['time'])
|
||||
return values, timestamps
|
||||
|
||||
def calculate(self, candles, num_results=1):
|
||||
|
||||
# Get a list of closing values and timestamps associated with these values.
|
||||
closes, ts = self.isolate_values('close', self.get_records('close', candles, num_results))
|
||||
|
||||
# Convert the list of closes to a numpy array
|
||||
np_real_data = np.array(closes, dtype=float)
|
||||
|
||||
# Pass the closing values and the period parameter to talib
|
||||
i_values = self.process(np_real_data, self.properties['period'])
|
||||
|
||||
# The first <period> of values returned from talib are all <NAN>.
|
||||
# They should get trimmed off.
|
||||
i_values = i_values[-num_results:]
|
||||
ts = ts[-num_results:]
|
||||
|
||||
# Set the current indicator value to the last value calculated.
|
||||
self.properties['value'] = round(float(i_values[-1]), 2)
|
||||
|
||||
# Combine the new data with the timestamps
|
||||
r_data = []
|
||||
for each in range(len(i_values)):
|
||||
r_data.append({'time': ts[each], 'value': i_values[each]})
|
||||
|
||||
# Return the data prefixed with the type of indicator.
|
||||
return {"type": self.properties['type'], "data": r_data}
|
||||
|
||||
def process(self, data, period):
|
||||
# Abstract function must be overloaded with the appropriate talib call.
|
||||
raise ValueError(f'Indicators: No talib call implemented : {self.name}')
|
||||
|
||||
|
||||
class Volume(Indicator):
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
|
||||
def calculate(self, candles, num_results=800):
|
||||
return candles.get_volume(self.properties['type'])
|
||||
# Override the calculate function because volume is
|
||||
# not calculated just fetched from candle data.
|
||||
def calculate(self, candles, num_results=1):
|
||||
# Request an array of the last volume values.
|
||||
r_data = candles.get_latest_vol(num_results)
|
||||
# Set the current volume to the last value returned.
|
||||
self.properties['value'] = float(r_data[-1]['value'])
|
||||
# Return the data prefixed with the type of indicator.
|
||||
return {"type": self.properties['type'], "data": r_data}
|
||||
|
||||
|
||||
indicator_types.append('Volume')
|
||||
|
||||
|
||||
class SMA(Indicator):
|
||||
|
|
@ -31,102 +96,62 @@ class SMA(Indicator):
|
|||
if 'period' not in properties:
|
||||
self.properties['period'] = 20
|
||||
|
||||
def calculate(self, candles, num_results=800):
|
||||
# These indicators do computations over period number of price data points.
|
||||
# So we need at least that plus what ever amount of results needed.
|
||||
num_cv = self.properties['period'] + num_results
|
||||
closing_data = candles.get_latest_close_values(num_cv)
|
||||
if len(closing_data) < num_cv:
|
||||
print(f'Could not calculate {self.properties["type"]} for time period of {self.properties["period"]}')
|
||||
print('Not enough data available')
|
||||
return
|
||||
# Initialize two arrays to hold a list of closing values and
|
||||
# a list of timestamps associated with these values
|
||||
closes = []
|
||||
ts = []
|
||||
# Isolate all the closing values and timestamps from
|
||||
# the dictionary object
|
||||
for each in closing_data:
|
||||
closes.append(each['close'])
|
||||
ts.append(each['time'])
|
||||
# Convert the list of closes to a numpy array
|
||||
np_real_data = np.array(closes, dtype=float)
|
||||
# Pass the closing values and the period parameter to talib
|
||||
i_values = None
|
||||
if self.properties['type'] == 'SMA':
|
||||
i_values = talib.SMA(np_real_data, self.properties['period'])
|
||||
if self.properties['type'] == 'RSI':
|
||||
i_values = talib.RSI(np_real_data, self.properties['period'])
|
||||
if self.properties['type'] == 'EMA':
|
||||
i_values = talib.EMA(np_real_data, self.properties['period'])
|
||||
if self.properties['type'] == 'LREG':
|
||||
i_values = talib.LINEARREG(np_real_data, self.properties['period'])
|
||||
def process(self, data, period):
|
||||
return talib.SMA(data, period)
|
||||
|
||||
# Combine the new data with the timestamps
|
||||
# Warning: The first <period> of rsi values are <NAN>.
|
||||
# But they should get trimmed off todo get rid of try except *just debugging info
|
||||
try:
|
||||
i_values = i_values[-num_results:]
|
||||
except Exception:
|
||||
raise ValueError(f'error: {self.properties.type} {i_values}')
|
||||
ts = ts[-num_results:]
|
||||
r_data = []
|
||||
for each in range(len(i_values)):
|
||||
r_data.append({'time': ts[each], 'value': i_values[each]})
|
||||
return {"type": self.properties['type'], "data": r_data}
|
||||
|
||||
indicator_types.append('SMA')
|
||||
|
||||
|
||||
class EMA(SMA):
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
|
||||
def process(self, data, period):
|
||||
return talib.EMA(data, period)
|
||||
|
||||
|
||||
indicator_types.append('EMA')
|
||||
|
||||
|
||||
class RSI(SMA):
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
|
||||
def process(self, data, period):
|
||||
return talib.RSI(data, period)
|
||||
|
||||
|
||||
indicator_types.append('RSI')
|
||||
|
||||
|
||||
class LREG(SMA):
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
|
||||
def process(self, data, period):
|
||||
return talib.LINEARREG(data, period)
|
||||
|
||||
|
||||
indicator_types.append('LREG')
|
||||
|
||||
|
||||
class ATR(SMA):
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
|
||||
def calculate(self, candles, num_results=800):
|
||||
# These indicators do computations over period number of price data points.
|
||||
# So we need at least that plus what ever amount of results needed.
|
||||
num_cv = self.properties.period + num_results
|
||||
|
||||
high_data = candles.get_latest_high_values(num_cv)
|
||||
low_data = candles.get_latest_low_values(num_cv)
|
||||
close_data = candles.get_latest_close_values(num_cv)
|
||||
if len(close_data) < num_cv:
|
||||
print(f'Couldn\'t calculate {self.properties.type} for time period of {self.properties.period}')
|
||||
print('Not enough data availiable')
|
||||
return
|
||||
def calculate(self, candles, num_results=1):
|
||||
# Initialize 4 arrays to hold a list of h/l/c values and
|
||||
# a list of timestamps associated with these values
|
||||
highs = []
|
||||
lows = []
|
||||
closes = []
|
||||
ts = []
|
||||
# Isolate all the values and timestamps from
|
||||
# the dictionary objects
|
||||
# Get a list of values and timestamps associated with them.
|
||||
highs, ts = self.isolate_values('high', self.get_records('high', candles, num_results))
|
||||
lows, ts = self.isolate_values('low', self.get_records('low', candles, num_results))
|
||||
closes, ts = self.isolate_values('close', self.get_records('close', candles, num_results))
|
||||
|
||||
for each in high_data:
|
||||
highs.append(each['high'])
|
||||
for each in low_data:
|
||||
lows.append(each['low'])
|
||||
for each in close_data:
|
||||
closes.append(each['close'])
|
||||
ts.append(each['time'])
|
||||
# Convert the lists to a numpy array
|
||||
np_highs = np.array(highs, dtype=float)
|
||||
np_lows = np.array(lows, dtype=float)
|
||||
np_closes = np.array(closes, dtype=float)
|
||||
|
||||
# Pass the closing values and the period parameter to talib
|
||||
atr = talib.ATR(high=np_highs,
|
||||
low=np_lows,
|
||||
|
|
@ -137,6 +162,10 @@ class ATR(SMA):
|
|||
# But they should get trimmed off
|
||||
atr = atr[-num_results:]
|
||||
ts = ts[-num_results:]
|
||||
|
||||
# Set the current indicator value to the last value calculated.
|
||||
self.properties['value'] = round(float(atr[-1]), 2)
|
||||
|
||||
r_data = []
|
||||
for each in range(len(atr)):
|
||||
# filter out nan values
|
||||
|
|
@ -146,6 +175,9 @@ class ATR(SMA):
|
|||
return {"type": self.properties.type, "data": r_data}
|
||||
|
||||
|
||||
indicator_types.append('ATR')
|
||||
|
||||
|
||||
class BolBands(Indicator):
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
|
|
@ -158,8 +190,8 @@ class BolBands(Indicator):
|
|||
self.properties['color_2'] = f"#{random.randrange(0x1000000):06x}"
|
||||
if 'color_3' not in properties:
|
||||
self.properties['color_3'] = ul_col
|
||||
if 'value1' not in properties:
|
||||
self.properties['value1'] = 0
|
||||
if 'value' not in properties:
|
||||
self.properties['value'] = 0
|
||||
if 'value2' not in properties:
|
||||
self.properties['value2'] = 0
|
||||
if 'value3' not in properties:
|
||||
|
|
@ -171,26 +203,10 @@ class BolBands(Indicator):
|
|||
if 'ma' not in properties:
|
||||
self.properties['ma'] = 1
|
||||
|
||||
def calculate(self, candles, num_results=800):
|
||||
# These indicators do computations over period number of price data points.
|
||||
# So we need at least that plus what ever amount of results needed.
|
||||
# Acceptable values for ma in the talib.BBANDS
|
||||
# {'SMA':0,'EMA':1, 'WMA' : 2, 'DEMA' : 3, 'TEMA' : 4, 'TRIMA' : 5, 'KAMA' : 6, 'MAMA' : 7, 'T3' : 8}
|
||||
num_cv = self.properties['period'] + num_results
|
||||
closing_data = candles.get_latest_close_values(num_cv)
|
||||
if len(closing_data) < num_cv:
|
||||
print(f'Couldn\'t calculate {self.properties["type"]} for time period of {self.properties["period"]}')
|
||||
print('Not enough data availiable')
|
||||
return
|
||||
# Initialize two arrays to hold a list of closing values and
|
||||
# a list of timestamps associated with these values
|
||||
closes = []
|
||||
ts = []
|
||||
# Isolate all the closing values and timestamps from
|
||||
# the dictionary object
|
||||
for each in closing_data:
|
||||
closes.append(each['close'])
|
||||
ts.append(each['time'])
|
||||
def calculate(self, candles, num_results=1):
|
||||
# Get a list of closing values and timestamps associated with these values.
|
||||
closes, ts = self.isolate_values('close', self.get_records('close', candles, num_results))
|
||||
|
||||
# Convert the list of closes to a numpy array
|
||||
np_real_data = np.array(closes, dtype=float)
|
||||
# Pass the closing values and the period parameter to talib
|
||||
|
|
@ -212,6 +228,12 @@ class BolBands(Indicator):
|
|||
r_data_u = []
|
||||
r_data_m = []
|
||||
r_data_l = []
|
||||
|
||||
# Set the current indicator values to the last value calculated.
|
||||
self.properties['value'] = round(float(i_values_u[-1]),2)
|
||||
self.properties['value2'] = round(float(i_values_m[-1]),2)
|
||||
self.properties['value3'] = round(float(i_values_l[-1]),2)
|
||||
|
||||
for each in range(len(i_values_u)):
|
||||
# filter out nan values
|
||||
if np.isnan(i_values_u[each]):
|
||||
|
|
@ -223,6 +245,9 @@ class BolBands(Indicator):
|
|||
return {"type": self.properties['type'], "data": r_data}
|
||||
|
||||
|
||||
indicator_types.append('BOLBands')
|
||||
|
||||
|
||||
class MACD(Indicator):
|
||||
def __init__(self, name, indicator_type, properties):
|
||||
super().__init__(name, indicator_type, properties)
|
||||
|
|
@ -247,14 +272,14 @@ class MACD(Indicator):
|
|||
# These indicators do computations over a period number of price data points.
|
||||
# So we need at least that plus what ever amount of results needed.
|
||||
# It seems it needs num_of_nans = (slow_p) - 2) + signal_p
|
||||
|
||||
# TODO: slow_p or fast_p which ever is greater should be used in the calc below.
|
||||
# TODO but i am investigating this.
|
||||
# Slow_p or fast_p which ever is greater should be used in the calc below.
|
||||
# TODO Investigating this should it be an error.
|
||||
if self.properties['fast_p'] > self.properties['slow_p']:
|
||||
raise ValueError('Error I think: TODO: calculate_macd()')
|
||||
raise ValueError('Possible Error: calculating MACD fast_period needs to higher then slow_period')
|
||||
# Not sure about the lookback period for macd algorithm below was a result of trial and error.
|
||||
num_cv = (self.properties['slow_p'] - 2) + self.properties['signal_p'] + num_results
|
||||
|
||||
closing_data = candles.get_latest_close_values(num_cv)
|
||||
closing_data = candles.get_latest_values(value_name='close', num_record=num_cv)
|
||||
if len(closing_data) < num_cv:
|
||||
print(f'Couldn\'t calculate {self.properties["type"]} for time period of {self.properties["slow_p"]}')
|
||||
print('Not enough data available')
|
||||
|
|
@ -281,15 +306,17 @@ class MACD(Indicator):
|
|||
# Warning: The first (<period> -1) of values are <NAN>.
|
||||
# But they should get trimmed off
|
||||
macd = macd[-num_results:]
|
||||
if len(macd) == 1:
|
||||
print('looks like after slicing')
|
||||
print(macd)
|
||||
signal = signal[-num_results:]
|
||||
hist = hist[-num_results:]
|
||||
ts = ts[-num_results:]
|
||||
r_macd = []
|
||||
r_signal = []
|
||||
r_hist = []
|
||||
|
||||
# Set the current indicator values to the last value calculated.
|
||||
self.properties['macd'] = round(float(macd[-1]), 2)
|
||||
self.properties['signal'] = round(float(signal[-1]), 2)
|
||||
|
||||
for each in range(len(macd)):
|
||||
# filter out nan values
|
||||
if np.isnan(macd[each]):
|
||||
|
|
@ -301,6 +328,9 @@ class MACD(Indicator):
|
|||
return {"type": self.properties['type'], "data": r_data}
|
||||
|
||||
|
||||
indicator_types.append('MACD')
|
||||
|
||||
|
||||
class Indicators:
|
||||
def __init__(self, candles, config):
|
||||
# Object containing Price and candle data.
|
||||
|
|
@ -315,11 +345,7 @@ class Indicators:
|
|||
self.create_loaded_indicators()
|
||||
|
||||
# Create a List of all available indicator types
|
||||
self.indicator_types = [ ]
|
||||
for i in self.indicator_list:
|
||||
if self.indicator_list[i]['type'] in self.indicator_types:
|
||||
continue
|
||||
self.indicator_types.append(self.indicator_list[i]['type'])
|
||||
self.indicator_types = indicator_types
|
||||
|
||||
# A list of values to use with bolenger bands
|
||||
self.bb_ma_val = {'SMA': 0, 'EMA': 1, 'WMA': 2, 'DEMA': 3, 'TEMA': 4, 'TRIMA': 5, 'KAMA': 6, 'MAMA': 7, 'T3': 8}
|
||||
|
|
@ -327,6 +353,7 @@ class Indicators:
|
|||
def create_loaded_indicators(self):
|
||||
for each in self.indicator_list:
|
||||
self.create_indicator(each, self.indicator_list[each]['type'], self.indicator_list[each])
|
||||
|
||||
@staticmethod
|
||||
def get_indicator_defaults():
|
||||
"""Set the default settings for each indicator"""
|
||||
|
|
@ -345,7 +372,7 @@ class Indicators:
|
|||
'RSI 8': {'type': 'RSI', 'period': 8, 'visible': True, 'color': f"#{random.randrange(0x1000000):06x}",
|
||||
'value': 0},
|
||||
'Bolenger': {'color_1': '#5ad858', 'color_2': '#f0f664', 'color_3': '#5ad858', 'devdn': 2, 'devup': 2,
|
||||
'ma': 1, 'period': 20, 'type': 'BOLBands', 'value': 0, 'value1': '38691.58',
|
||||
'ma': 1, 'period': 20, 'type': 'BOLBands', 'value': '38691.58',
|
||||
'value2': '38552.36',
|
||||
'value3': '38413.14', 'visible': 'True'},
|
||||
'vol': {'type': 'Volume', 'visible': True, 'value': 0}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
class Alert{
|
||||
constructor(alert_type, source, state) {
|
||||
// The type of alert.
|
||||
this.type = alert_type;
|
||||
// The source of the alert.
|
||||
this.source = source;
|
||||
// Other info in the alert.
|
||||
this.state = state;
|
||||
// The alert messages.
|
||||
this.msg = 'Signal state change: ' + this.source + ' = ' + this.state;
|
||||
}
|
||||
alert_source(){
|
||||
return this.source;
|
||||
}
|
||||
alert_type(){
|
||||
return this.type;
|
||||
}
|
||||
alert_state(){
|
||||
return this.state;
|
||||
}
|
||||
alert_msg(){
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
|
||||
class Alerts {
|
||||
constructor(target_id) {
|
||||
// The list of alert messages.
|
||||
this.alerts = [];
|
||||
// The html element id that displays the alert messages.
|
||||
this.target_id = target_id;
|
||||
// The html element that displays the alert messages.
|
||||
this.target = null;
|
||||
}
|
||||
publish_alerts(alert_type, data){
|
||||
if (alert_type == 'signal_changes'){
|
||||
// If the alert_type is signal changes then data will
|
||||
// contain a list of objects with format: { name: str, state: bool }
|
||||
console.log('publishing alerts')
|
||||
for(let sig in data){
|
||||
console.log('publishing single alert');
|
||||
this.alerts.push( new Alert('signal', sig, data[sig]) );
|
||||
}
|
||||
this.update_html();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
update_html(){
|
||||
let alerts ='';
|
||||
for (let index in this.alerts){
|
||||
let alert = this.alerts[index].alert_msg();
|
||||
alerts += '<span>' + alert + '</span><br>';
|
||||
}
|
||||
this.target.innerHTML = alerts;
|
||||
}
|
||||
|
||||
set_target(){
|
||||
// This is called after the html document has been parsed.
|
||||
this.target = document.getElementById(this.target_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
class Strategy{
|
||||
constructor(strat_type, name, side, take_profit, stop_loss, conditions) {
|
||||
// The type of alert.
|
||||
this.type = strat_type;
|
||||
// The source of the alert.
|
||||
this.name = name;
|
||||
// buy|sell: string
|
||||
this.side = side;
|
||||
// Conditions to exit trade in profit. {on: ('profit'|'condition'), trig_val: 999}
|
||||
this.take_profit = take_profit;
|
||||
// Conditions to exit trade in loss. {on: ('loss'|'condition'), trig_val: 999}
|
||||
this.stop_loss = stop_loss;
|
||||
// The conditions to evaluate. { on: (signal_name: str), trig_val: (true|false|changed) }
|
||||
this.conditions = conditions;
|
||||
// html formatted representation.
|
||||
this.display_output = this.format_html();
|
||||
// any last results needed for comparison.
|
||||
this.last_result = {};
|
||||
}
|
||||
format_html(){
|
||||
let html ='<li>' + '<span>' + this.name + '</span>';
|
||||
html +='<span>' + this.type + '</span>';
|
||||
html +='<span>' + this.side + '</span>';
|
||||
if (this.take_profit.on == 'profit'){
|
||||
if(this.side == 'buy') {var then_do ='sell';}
|
||||
else if(this.side == 'sell') {var then_do ='buy';}
|
||||
html += '<span>' + 'If profit exceeds' + ': ' + this.take_profit.trig_val + ' '+ then_do + '.</span>';
|
||||
}
|
||||
if (this.stop_loss.on == 'loss'){
|
||||
if(this.side == 'buy') {var then_do ='sell';}
|
||||
else if(this.side == 'sell') {var then_do ='buy';}
|
||||
html +='<span>' + 'If loss exceeds' + ': ' + this.stop_loss.trig_val + ' '+ then_do + '.</span>';
|
||||
}
|
||||
for(let cond of this.conditions){
|
||||
html += '<span>If ' + cond.on + ' is ' + cond.trig_val + '</span>';
|
||||
}
|
||||
html += '</li>';
|
||||
return html;
|
||||
}
|
||||
conditions_satisfied(signals){
|
||||
let result = true;
|
||||
for(let cond of this.conditions){
|
||||
if (cond.trig_val == 'true'){
|
||||
if (signals[cond.on] == true){
|
||||
result = result && true;
|
||||
}else{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else if (cond.trig_val == 'false'){
|
||||
if (signals[cond.on] == false){
|
||||
result = result && true;
|
||||
}else{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else if (cond.trig_val == 'changed'){
|
||||
// If no last result exists for this trigger, create one.
|
||||
if ( !this.last_results[cond.on] ){
|
||||
this.last_results[cond.on] = signals[cond.on];
|
||||
}
|
||||
if (signals[cond.on] != this.last_results[cond.on]){
|
||||
result = result && true;
|
||||
}else{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
name(){
|
||||
return this.name;
|
||||
}
|
||||
type(){
|
||||
return this.type;
|
||||
}
|
||||
strategy(){
|
||||
return this.strategy;
|
||||
}
|
||||
print_html(){
|
||||
return this.display_output;
|
||||
}
|
||||
}
|
||||
class Strategies {
|
||||
constructor(target_id) {
|
||||
// The list of strategies.
|
||||
this.strategies = [];
|
||||
// The html element id that displays the strategies.
|
||||
this.target_id = target_id;
|
||||
// The html element that displays the the strategies.
|
||||
this.target = null;
|
||||
|
||||
}
|
||||
// Call to display Create new signal dialog.
|
||||
open_form() { document.getElementById("new_strat_form").style.display = "grid"; }
|
||||
// Call to hide Create new signal dialog.
|
||||
close_form() { document.getElementById("new_strat_form").style.display = "none"; }
|
||||
|
||||
open_stg_form(){
|
||||
this.open_form();
|
||||
this.fill_field('strat_opt', 'take_profit');
|
||||
// condition = { on: (signal_name: str), trig_val: (true|false|changed) }
|
||||
let cond1 = {on: 'signal1', trig_val:'changed'}
|
||||
// take_profit = {on: ('profit'|'condition'), trig_val: 999}
|
||||
let take_profit = {on: 'signal2', trig_val: false}
|
||||
// stop_loss = {on: ('loss'|'condition'), trig_val: 999}
|
||||
let stop_loss = {on: 'loss', trig_val: '80%' }
|
||||
let side='buy';
|
||||
let conditions = []; //_str = side + condition + take_profit
|
||||
conditions.push(cond1);
|
||||
this.strategies.push( new Strategy('take_profit', 'strategy_#1', side, take_profit, stop_loss, conditions) );
|
||||
this.update_html();
|
||||
}
|
||||
fill_field(field, value){
|
||||
if (field == 'strat_opt'){
|
||||
let options = document.getElementById('strat_opt');
|
||||
|
||||
if (value == 'take_profit'){
|
||||
// Clear previous content.
|
||||
options.innerHTML="";
|
||||
|
||||
//Create a drop down for buy/sell option
|
||||
let side_select_label = document.createElement("label");
|
||||
side_select_label.for = 'trade_in_side';
|
||||
side_select_label.innerHTML = 'Side:';
|
||||
let side_select = document.createElement("select");
|
||||
side_select.id = "trade_in_side";
|
||||
side_select.name = "trade_in_side";
|
||||
side_select.innerHTML = '<option>buy</option><option>sell</option>';
|
||||
options.appendChild(side_select_label);
|
||||
options.appendChild(side_select);
|
||||
|
||||
//Create an input for margin trading value. (1X-100X)
|
||||
let margin_label = document.createElement("label");
|
||||
margin_label.for = 'margin';
|
||||
margin_label.innerHTML = 'Margin:';
|
||||
let margin_select = document.createElement("input");
|
||||
margin_select.name = 'margin';
|
||||
margin_select.type = 'number';
|
||||
margin_select.min = 1;
|
||||
margin_select.max = 100;
|
||||
margin_select.value = 1;
|
||||
options.appendChild(margin_label);
|
||||
options.appendChild(margin_select);
|
||||
|
||||
// Create a un ordered list to hold the conditions of trading in.
|
||||
let ul_in_cond = document.createElement('ul');
|
||||
ul_in_cond.id ='trade_in_conditions';
|
||||
|
||||
// Create a submit button for the conditions.
|
||||
let add_cond_btn = document.createElement('button');
|
||||
add_cond_btn.setAttribute('id','add_cond_btn');
|
||||
add_cond_btn.setAttribute('type','button');
|
||||
add_cond_btn.innerHTML = "Add Condition";
|
||||
add_cond_btn.onclick = function () {
|
||||
let li = document.createElement('li');
|
||||
li.innerHTML = 'Trigger:'+ trigger.value+' Value:' + trigVal.value;
|
||||
ul_in_cond.appendChild(li);
|
||||
};
|
||||
|
||||
// Add a horizontal rule.
|
||||
let element = document.createElement('hr');
|
||||
options.appendChild(element);
|
||||
|
||||
//Create a drop down for trigger options
|
||||
let trigger_label = document.createElement("label");
|
||||
trigger_label.for = 'trigger';
|
||||
trigger_label.innerHTML = 'Trigger Signal:';
|
||||
let trigger = document.createElement("select");
|
||||
trigger.id = "trigger";
|
||||
trigger.name = "trigger";
|
||||
// Populate the signal selector.
|
||||
for (let signal in window.UI.signals.signals){
|
||||
let opt = document.createElement('option');
|
||||
opt.value = window.UI.signals.signals[signal].name;
|
||||
opt.innerHTML = window.UI.signals.signals[signal].name;
|
||||
trigger.appendChild(opt);
|
||||
}
|
||||
// Add it to the dom.
|
||||
options.appendChild(trigger_label);
|
||||
options.appendChild(trigger);
|
||||
|
||||
//Create a drop down for trigger value.
|
||||
let trigVal_label = document.createElement("label");
|
||||
trigVal_label.for = 'trigVal';
|
||||
trigVal_label.innerHTML = 'Trigger Value:';
|
||||
let trigVal = document.createElement("select");
|
||||
trigVal.id = "trigVal";
|
||||
trigVal.name = "trigVal";
|
||||
|
||||
let opt = document.createElement('option');
|
||||
opt.value = true;
|
||||
opt.innerHTML = 'true';
|
||||
trigVal.appendChild(opt);
|
||||
|
||||
opt = document.createElement('option');
|
||||
opt.value = false;
|
||||
opt.innerHTML = 'false';
|
||||
trigVal.appendChild(opt);
|
||||
|
||||
opt = document.createElement('option');
|
||||
opt.value = 'changed';
|
||||
opt.innerHTML = 'changed';
|
||||
trigVal.appendChild(opt);
|
||||
|
||||
// Add trigger Value select element to the dom.
|
||||
options.appendChild(trigVal_label);
|
||||
options.appendChild(trigVal);
|
||||
|
||||
// Add the submit btn and the list to the dom.
|
||||
options.appendChild(add_cond_btn);
|
||||
options.appendChild(ul_in_cond);
|
||||
|
||||
// Add a horizontal rule.
|
||||
element = document.createElement('hr');
|
||||
options.appendChild(element);
|
||||
|
||||
//Create an input for take profit value.
|
||||
let prof_value_lbl = document.createElement("label");
|
||||
prof_value_lbl.for = 'profit_val';
|
||||
prof_value_lbl.innerHTML = 'Profit %:';
|
||||
let profit_val = document.createElement("input");
|
||||
profit_val.type='number';
|
||||
profit_val.min=0; profit_val.max=100; profit_val.value = 50;
|
||||
|
||||
//Create a drop down for take profit type.
|
||||
let prof_typ_label = document.createElement("label");
|
||||
prof_typ_label.for = 'prof_typ';
|
||||
prof_typ_label.innerHTML = 'Profit type:';
|
||||
let prof_typ = document.createElement("select");
|
||||
prof_typ.id = "prof_typ";
|
||||
prof_typ.name = "prof_typ";
|
||||
prof_typ.onchange= function(){
|
||||
if (this.value == 'value'){
|
||||
prof_value_lbl.style.display = 'inline-block';
|
||||
profit_val.style.display = 'inline-block';
|
||||
//profit_trig.style.display = 'none';
|
||||
}
|
||||
else if (this.value == 'conditional'){
|
||||
prof_value_lbl.style.display = 'none';
|
||||
profit_val.style.display = 'none';
|
||||
//profit_trig.style.display = 'block';
|
||||
}
|
||||
};
|
||||
|
||||
opt = document.createElement('option');
|
||||
opt.value = 'value';
|
||||
opt.innerHTML = 'value';
|
||||
prof_typ.appendChild(opt);
|
||||
|
||||
opt = document.createElement('option');
|
||||
opt.value = 'conditional';
|
||||
opt.innerHTML = 'conditional';
|
||||
prof_typ.appendChild(opt);
|
||||
|
||||
// Add profit_type select element to the dom.
|
||||
options.appendChild(prof_typ_label);
|
||||
options.appendChild(prof_typ);
|
||||
|
||||
opt = document.createElement('br');
|
||||
options.appendChild(opt);
|
||||
|
||||
// Add value input to the dom.
|
||||
options.appendChild(prof_value_lbl);
|
||||
options.appendChild(profit_val);
|
||||
|
||||
|
||||
}
|
||||
if (value == 'incremental_profits'){
|
||||
options.innerHTML="Incremental_profits -> not done.";
|
||||
}
|
||||
if (value == 'swing'){
|
||||
options.innerHTML="swing -> not done.";
|
||||
}
|
||||
}
|
||||
}
|
||||
set_target(){
|
||||
// This is called after the html document has been parsed.
|
||||
this.target = document.getElementById(this.target_id);
|
||||
}
|
||||
update_html(){
|
||||
let strats ='';
|
||||
for (let strat of this.strategies){
|
||||
strats += strat.print_html();
|
||||
}
|
||||
this.target.innerHTML = strats;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -241,7 +241,6 @@ input[type="checkbox"] {
|
|||
display:grid;
|
||||
grid-template-columns:1fr 1fr;
|
||||
min-height: 100px;
|
||||
min-height: 100px;
|
||||
}
|
||||
#balances{
|
||||
width:250px;
|
||||
|
|
@ -261,6 +260,12 @@ position: absolute;
|
|||
right: -17px; /* Increase/Decrease this value for cross-browser compatibility */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#alerts_content{
|
||||
overflow: scroll;
|
||||
}
|
||||
#signal_content, #strats_content, #strat_opt{
|
||||
overflow: scroll;
|
||||
}
|
||||
#trade_content{
|
||||
margin-top:8px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ class Comms {
|
|||
this.on_candle_update = ocu;
|
||||
this.on_candle_close = occ;
|
||||
this.on_indctr_update = oiu;
|
||||
this.connection_open = false;
|
||||
}
|
||||
|
||||
candle_update(new_candle){
|
||||
|
|
@ -32,13 +33,22 @@ class Comms {
|
|||
return id;
|
||||
}
|
||||
send_to_app(message_type, data){
|
||||
console.log( JSON.stringify({ message_type: message_type, data : data }));
|
||||
this.app_con.send( JSON.stringify({ message_type: message_type, data : data }));
|
||||
console.log( JSON.stringify({ message_type: message_type, data : data }));
|
||||
if (this.connection_open){
|
||||
this.app_con.send( JSON.stringify({ message_type: message_type, data : data }));
|
||||
}else{
|
||||
setTimeout(function(){
|
||||
window.UI.data.comms.app_con.send( JSON.stringify({ message_type: message_type, data : data }))
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
set_app_con(){
|
||||
// Create a web socket connection to our app.
|
||||
this.app_con = new WebSocket('ws://localhost:5000/ws');
|
||||
this.app_con.onopen = () => this.app_con.send("Connection OK");
|
||||
this.app_con.onopen = () => {
|
||||
this.app_con.send("Connection OK");
|
||||
this.connection_open = true;
|
||||
}
|
||||
|
||||
this.app_con.addEventListener('message', ev => {
|
||||
if(ev.data){
|
||||
|
|
@ -52,9 +62,39 @@ class Comms {
|
|||
}
|
||||
// Handle a reply from the server
|
||||
if (msg.reply) {
|
||||
// Handle indicator updates
|
||||
if (msg.reply == 'i_updates'){
|
||||
this.indicator_update(msg.data)
|
||||
// Handle updates from server.
|
||||
if (msg.reply == 'updates'){
|
||||
if ('i_updates' in msg.data){
|
||||
/* If the received message contains indicator updates.
|
||||
Forward them to the indicator instance. */
|
||||
this.indicator_update(msg.data['i_updates']);
|
||||
// Forward the indicator updates to the signal instance.
|
||||
window.UI.signals.i_update(msg.data['i_updates']);
|
||||
}
|
||||
if ('s_updates' 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 updates = msg.data['s_updates'];
|
||||
window.UI.signals.update_signal_states(updates);
|
||||
window.UI.alerts.publish_alerts('signal_changes', updates);
|
||||
}
|
||||
}else if (msg.reply == 'signals'){
|
||||
/* On initialization the server will send a list of signals loaded from file.
|
||||
Forward this to the signals instance.*/
|
||||
window.UI.signals.set_data(msg.data);
|
||||
}
|
||||
else if (msg.reply =='signal_created'){
|
||||
/* After the server creates a new signal it will send the details here.
|
||||
Forward this to the signals instance. I re-used the init function.
|
||||
But wrapped the data in a list element.*/
|
||||
let list_of_one = new Array();
|
||||
list_of_one.push(msg.data);
|
||||
window.UI.signals.set_data(list_of_one);
|
||||
}
|
||||
else {
|
||||
console.log(msg.reply)
|
||||
console.log(msg.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,23 +10,12 @@
|
|||
// this.height = height;
|
||||
// }
|
||||
//}
|
||||
//class Strategies {
|
||||
// constructor() {
|
||||
// this.height = height;
|
||||
// }
|
||||
//}
|
||||
//class Exchange_Info {
|
||||
// constructor() {
|
||||
// this.height = height;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//class Alerts {
|
||||
// constructor() {
|
||||
// this.height = height;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//class Header {
|
||||
// constructor() {
|
||||
// this.height = height;
|
||||
|
|
@ -52,7 +41,22 @@ class User_Interface{
|
|||
up-to-date configurable and variable data for the UI */
|
||||
this.data = new Data();
|
||||
|
||||
/* These classes interact with HTML elements that need to be parsed first */
|
||||
/* The object that handles the interface controls.*/
|
||||
this.controls = new Controls();
|
||||
|
||||
/* The object that handles the signals interface.*/
|
||||
this.signals = new Signals(this.data.indicators);
|
||||
|
||||
/* The object that handles alerts. Pass in the html
|
||||
element that will hold the list of alerts*/
|
||||
this.alerts = new Alerts("alert_list");
|
||||
|
||||
/* The object that handles alerts. Pass in the html
|
||||
element that will hold the list of alerts*/
|
||||
this.strats = new Strategies("strats_display");
|
||||
|
||||
/* These classes interact with HTML elements that need to be parsed first.
|
||||
TODO: Can any of these objects be created then run init functions after loading?*/
|
||||
window.addEventListener('load', function () {
|
||||
/* Charts object is responsible for maintaining the
|
||||
data visualisation area in the UI. */
|
||||
|
|
@ -77,12 +81,14 @@ class User_Interface{
|
|||
/* Point the callback fired when indicator updates are received
|
||||
to the indicator class object.*/
|
||||
window.UI.data.set_i_updates(window.UI.indicators.update);
|
||||
// Request data from the server. Receives it in a callback function then injects the html.
|
||||
window.UI.signals.request_signals();
|
||||
// initialize the alerts instance.
|
||||
window.UI.alerts.set_target();
|
||||
// initialize the alerts instance.
|
||||
window.UI.strats.set_target();
|
||||
});
|
||||
|
||||
/* The object that handles the interface controls.*/
|
||||
this.controls = new Controls();
|
||||
/* The object that handles the signals interface.*/
|
||||
this.signals = new Signals(this.data.indicators);
|
||||
}
|
||||
}
|
||||
UI = new User_Interface();
|
||||
|
|
|
|||
|
|
@ -200,14 +200,14 @@ class Bolenger extends Indicator{
|
|||
|
||||
init(data){
|
||||
// Initialize the data with the data object provided.
|
||||
this.setLine('line_u',data[0],'value1');
|
||||
this.setLine('line_u',data[0],'value');
|
||||
this.setLine('line_m',data[1],'value2');
|
||||
this.setLine('line_l',data[2],'value3');
|
||||
}
|
||||
|
||||
update(data){
|
||||
// Update the line-set data in the chart
|
||||
this.updateLine('line_u', data[0][0], 'value1');
|
||||
this.updateLine('line_u', data[0][0], 'value');
|
||||
// Update the line-set data in the chart
|
||||
this.updateLine('line_m', data[1][0], 'value2');
|
||||
// Update the line-set data in the chart
|
||||
|
|
@ -285,7 +285,6 @@ class Indicators {
|
|||
}
|
||||
|
||||
update(updates){
|
||||
console.log(updates);
|
||||
for (name in updates){
|
||||
window.UI.indicators.i_objs[name].update(updates[name].data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,94 @@
|
|||
class Signals {
|
||||
constructor(indicators) {
|
||||
this.indicators = indicators;
|
||||
this.signals=[];
|
||||
}
|
||||
// Call to display Create new signal dialog.
|
||||
open_signal_Form() { document.getElementById("new_sig_form").style.display = "grid"; }
|
||||
// Call to hide Create new signal dialog.
|
||||
close_signal_Form() { document.getElementById("new_sig_form").style.display = "none"; }
|
||||
|
||||
request_signals(){
|
||||
window.UI.data.comms.send_to_app('request', 'signals');
|
||||
}
|
||||
delete_signal(signal_name){
|
||||
window.UI.data.comms.send_to_app('delete_signal', signal_name);
|
||||
// Get the child element node
|
||||
let child = document.getElementById(signal_name + '_item');
|
||||
// Remove the child element from the document
|
||||
child.parentNode.removeChild(child);
|
||||
}
|
||||
i_update(updates){
|
||||
// Update the values listed in the signals section.
|
||||
for (let signal in this.signals){
|
||||
let value1 = updates[this.signals[signal].source1].data[0][this.signals[signal].prop1];
|
||||
this.signals[signal].value1 = value1.toFixed(2);
|
||||
if (this.signals[signal].source2 != 'value'){
|
||||
let value2 = updates[this.signals[signal].source2].data[0][this.signals[signal].prop2];
|
||||
this.signals[signal].value2 = value2.toFixed(2);
|
||||
}
|
||||
document.getElementById(this.signals[signal].name + '_value1').innerHTML = this.signals[signal].value1;
|
||||
document.getElementById(this.signals[signal].name + '_value2').innerHTML = this.signals[signal].value2;
|
||||
}
|
||||
}
|
||||
update_signal_states(s_updates){
|
||||
for (name in s_updates){
|
||||
let id = name + '_state'
|
||||
let span = document.getElementById(id);
|
||||
span.innerHTML = s_updates[name];
|
||||
console.log('state change!');
|
||||
console.log(name);
|
||||
}
|
||||
|
||||
}
|
||||
set_data(signals){
|
||||
// Create a list item for every signal and add it to a UL element.
|
||||
var ul = document.getElementById("signal_list");
|
||||
|
||||
// loop through a provided list of signals and attributes.
|
||||
for (let sig in signals){
|
||||
|
||||
// Create a Json object from each signals.
|
||||
// TODO: Clean up this inconsistent code.
|
||||
// During initialization this receives the object in string form.
|
||||
// when the object is created this function receives an object.
|
||||
if (typeof(signals[sig]) == 'string'){
|
||||
var obj = JSON.parse(signals[sig]);
|
||||
}else {var obj=signals[sig];}
|
||||
// Keep a local record of the signals.
|
||||
this.signals.push(obj);
|
||||
// Define the function that is called when deleting an individual signal.
|
||||
let click_func = "window.UI.signals.delete_signal('" + obj.name+ "')";
|
||||
// create a delete button for every individual signal.
|
||||
let delete_btn = "<button onclick='" + click_func + "' style='color:red;'>✘</button>";
|
||||
|
||||
// Put all the attributes into html elements.
|
||||
let signal_name = " <span>" + obj.name + ": </span>";
|
||||
let signal_state = "<span id='" + obj.name + "_state'>" + obj.state + "</span><br>";
|
||||
let signal_source1 = "<span>" + obj.source1 + "(" + obj.prop1 + ") </span>";
|
||||
let signal_val1 = "<span id='" + obj.name + "_value1'>" + obj.value1 + "</span>";
|
||||
let operator = " " + obj.operator + " ";
|
||||
let signal_source2 = "<span>" + obj.source2 + "(" + obj.prop2 + ") </span>";
|
||||
let signal_val2 = "<span id='" + obj.name + "_value2'>" + obj.value2 + "</span>";
|
||||
|
||||
// Stick all the html together.
|
||||
let html = delete_btn;
|
||||
html += signal_name + signal_state;
|
||||
html += signal_source1 + signal_val1;
|
||||
html += operator;
|
||||
html += signal_source2 + signal_val2;
|
||||
|
||||
// Create the list item.
|
||||
let li = document.createElement("li");
|
||||
// Give it an id.
|
||||
li.id = obj.name + '_item';
|
||||
// Inject the html.
|
||||
li.innerHTML= html;
|
||||
// And add it the the UL we created earlier.
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
fill_prop(target_id, indctr){
|
||||
// arg1: Id of of a selection element.
|
||||
// arg2: Name of an indicator
|
||||
|
|
@ -143,28 +225,30 @@ class Signals {
|
|||
|
||||
// Collect all the input fields.
|
||||
|
||||
var sigName = document.getElementById('signal_name').value; // The name of the New Signal.
|
||||
var sigSource = document.getElementById('sig_source').value; // The source(indicator) of the signal.
|
||||
var sigProp = document.getElementById('sig_prop').value; // The property to evaluate.
|
||||
var sig2Source = document.getElementById('sig2_source').value; // The second source if selected.
|
||||
var sig2Prop = document.getElementById('sig2_prop').value; // The second property to evaluate.
|
||||
var name = document.getElementById('signal_name').value; // The name of the New Signal.
|
||||
var source1 = document.getElementById('sig_source').value; // The source(indicator) of the signal.
|
||||
var prop1 = document.getElementById('sig_prop').value; // The property to evaluate.
|
||||
var source2 = document.getElementById('sig2_source').value; // The second source if selected.
|
||||
var prop2 = document.getElementById('sig2_prop').value; // The second property to evaluate.
|
||||
var operator = document.querySelector('input[name="Operator"]:checked').value; // The operator this evaluation will use.
|
||||
var range = document.getElementById('rangeVal').value; // The value of any range being evaluated.
|
||||
var sigType = document.getElementById('select_s_type').value; // The type of signal value or indicator comparison.
|
||||
var value = document.getElementById('value').value; // The input value if it is a value comparison.
|
||||
|
||||
var state = false;
|
||||
if (sigType == 'Comparison'){
|
||||
var sig2Source = sig2Source;
|
||||
var sig2Prop = sig2Prop;
|
||||
var source2 = source2;
|
||||
var prop2 = prop2;
|
||||
}else{
|
||||
var sig2Source = 'value';
|
||||
var sig2Prop = value;
|
||||
var source2 = 'value';
|
||||
var prop2 = value;
|
||||
}
|
||||
var value1 = null;
|
||||
var value2 = null;
|
||||
if (operator == "+/-" ){
|
||||
var range = {range : range};
|
||||
var data = {sigName, sigSource, sigProp, operator, sig2Source, sig2Prop, range};
|
||||
var data = {name, source1, prop1, operator, source2, prop2, range, state, value1, value2};
|
||||
}else{
|
||||
var data = {sigName, sigSource, sigProp, operator, sig2Source, sig2Prop};
|
||||
var data = {name, source1, prop1, operator, source2, prop2, state, value1, value2};
|
||||
}
|
||||
/* It may be more maintainable to configure the connection inside the different classes
|
||||
than passing functions, references and callbacks around. */
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
bt_data = get_init_data({{js_data|tojson}});
|
||||
</script>
|
||||
<!-- Load javascript -->
|
||||
<script src="{{ url_for('static', filename='Alerts.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='strategies.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='data.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='indicators.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='charts.js') }}"></script>
|
||||
|
|
@ -22,6 +24,35 @@
|
|||
|
||||
<body>
|
||||
<!-- Pop up forms for interface -->
|
||||
|
||||
<div class="form-popup" id="new_strat_form">
|
||||
<form action="/new_strategy" class="form-container">
|
||||
<!-- Panel 1 of 1 (5 rows, 2 columns) -->
|
||||
<div id="strat_pan_1" class="form_panels" style="grid-template-columns:repeat(2,1fr);grid-template-rows: repeat(5,1fr);">
|
||||
<!-- Panel title (row 1/5)-->
|
||||
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Strategy</h1>
|
||||
<!-- Name Input field (row 2/5)-->
|
||||
<div id = "strat_name_div" style="grid-column: 1 / span 2; grid_row:2;">
|
||||
<label for='stg_name' >Strategy Name:</label>
|
||||
<input type="text" id="stg_name" name="strat_name" />
|
||||
</div>
|
||||
<!-- Source Input field (row 3/5)-->
|
||||
<label for="strat_type" style="grid-column: 1; grid-row: 3;"><b>Strategy type</b></label>
|
||||
<select name="strat_type" id="strat_type" style="grid-column: 2; grid-row: 3;" onchange= "UI.strats.fill_field('strat_opt', this.value)">
|
||||
<option>take_profit</option>
|
||||
<option>incremental_profits</option>
|
||||
<option>swing</option>
|
||||
</select>
|
||||
<div id="strat_opt"></div>
|
||||
<div style="grid-column: 1 / span 2; grid-row: 5;">
|
||||
<button type="button" class="btn cancel" onclick="UI.strats.close_form()">Close</button>
|
||||
<button type="button" class="btn next" onclick="UI.strats.next(1)">Next</button>
|
||||
</div>
|
||||
|
||||
</div><!----End panel 1--------->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="form-popup" id="new_sig_form">
|
||||
<form action="/new_signal" class="form-container">
|
||||
<!-- Panel 1 of 3 (5 rows, 2 columns) -->
|
||||
|
|
@ -191,8 +222,8 @@
|
|||
<div id="right_panel">
|
||||
|
||||
<button class="collapsible bg_red" id="alerts_btn">Alerts</button>
|
||||
<div class="content">
|
||||
<p>No Alerts</p>
|
||||
<div class="content" id="alerts_content">
|
||||
<p id="alert_list">No Alerts</p>
|
||||
</div>
|
||||
<button class="collapsible bg_blue">Exchange Info</button>
|
||||
<div id="bal_content" class="content">
|
||||
|
|
@ -233,16 +264,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<button class="collapsible bg_blue">Signals</button>
|
||||
<div class="content">
|
||||
<div class="content" id="signal_content">
|
||||
<button class="new_btn" id="new_signal" onclick="UI.signals.open_signal_Form()">New Signal</button>
|
||||
<hr>
|
||||
<h3>Signals</h3>
|
||||
<div><ul id="signal_list"></ul></div>
|
||||
</div>
|
||||
<button class="collapsible bg_blue">Strategies</button>
|
||||
<div class="content">
|
||||
<button class="new_btn">New Strategy</button>
|
||||
<div class="content" id="strats_content">
|
||||
<button class="new_btn" id="new_strats_btn" onclick="UI.strats.open_stg_form()">New Strategy</button>
|
||||
<hr>
|
||||
<h3>Strategies</h3>
|
||||
<div><ul id="strats_display"></ul></div>
|
||||
</div>
|
||||
<button class="collapsible bg_blue">Statistics</button>
|
||||
<div class="content">
|
||||
|
|
|
|||
Loading…
Reference in New Issue