brighter-trading/Signals.py

181 lines
6.4 KiB
Python

import json
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, loaded_signals=None):
# list of Signal objects.
self.signals = []
# Initialize signals with loaded data.
if loaded_signals is not None:
self.create_signal_from_dic(loaded_signals)
@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 create_signal_from_dic(self, signals_list=None):
"""
:param signals_list: list of dict
:return True: on success.
Create and store signal objects from list of dictionaries.
"""
# If no signals were provided used a default list.
if signals_list is None:
signals_list = self.get_signals_defaults()
# Loop through the provided list, unpack the dictionaries, create and store the signal objects.
for sig in signals_list:
self.new_signal(sig)
return True
def get_signals(self, form):
# Return a python object of all the signals stored in this instance.
if form == 'obj':
return self.signals
# Return a JSON object of all the signals stored in this instance.
elif form == 'json':
sigs = self.signals
json_str = []
for sig in sigs:
# TODO: note - Explore why I had to treat signals and strategies different here.
json_str.append(json.dumps(sig.__dict__))
return json_str
# Return a dictionary object of all the signals stored in this instance.
elif form == 'dict':
sigs = self.signals
s_list = []
for sig in sigs:
dic = sig.__dict__
s_list.append(dic)
return s_list
def get_signal_by_name(self, name):
for signal in self.signals:
if signal.name == name:
return signal
return None
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