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:
Rob 2022-06-04 00:11:48 -03:00
parent de4686b109
commit 5f5f34c945
17 changed files with 958 additions and 72796 deletions

View File

@ -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"""

File diff suppressed because it is too large Load Diff

153
Signals.py Normal file
View File

@ -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

File diff suppressed because it is too large Load Diff

24
app.py
View File

@ -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

View File

@ -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}

View File

@ -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
View File

@ -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()

View File

@ -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}

63
static/Alerts.js Normal file
View File

@ -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);
}
}

289
static/Strategies.js Normal file
View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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)
}
}
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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;'>&#10008;</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. */

View File

@ -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">