Added support for multiple timeframes for coefficient calculation.

This commit is contained in:
Jamie Cash
2021-03-08 17:50:24 +00:00
parent 15f1ca2288
commit d08fef0cbb
4 changed files with 177 additions and 113 deletions
+7 -4
View File
@@ -8,8 +8,11 @@ calculate:
overlap_pct: 90
max_p_value: 0.05
monitor:
from:
minutes: 30
calculate_from:
long:
minutes: 30
short:
minutes: 10
interval: 10
min_prices: 400
max_set_size_diff_pct: 90
@@ -58,8 +61,8 @@ logging:
developer:
inspection: false
window:
x: 42
y: 51
x: 37
y: 42
width: 1512
height: 975
style: 541072960
+111 -75
View File
@@ -36,7 +36,12 @@ class Correlation:
# The price data used to calculate the correlations
__price_data = None
# Coefficient data and history. Will be created as dataframes in Init
# The shortest timeframe (which is the largest value) for calculate_from. This will be used when we update
# the coefficient_data dataframe. All calculations for all values specified in calculate_from will be stored in
# coefficient_history, however only the shortest timeframe will be updated in coefficient_data.
__shortest_timeframe = None
# Coefficient data and history. Will be created in init call to __reset_coefficient_data
coefficient_data = None
coefficient_history = None
@@ -179,7 +184,7 @@ class Correlation:
# If we were monitoring, we stopped, so start again.
if was_monitoring:
self.start_monitor(interval=self.__monitoring_params['interval'],
from_mins=self.__monitoring_params['from_mins'],
calculate_from=self.__monitoring_params['calculate_from'],
min_prices=self.__monitoring_params['min_prices'],
max_set_size_diff_pct=self.__monitoring_params['max_set_size_diff_pct'],
overlap_pct=self.__monitoring_params['overlap_pct'],
@@ -197,14 +202,15 @@ class Correlation:
return price_data
def start_monitor(self, interval, from_mins, min_prices=100, max_set_size_diff_pct=90, overlap_pct=90,
max_p_value=0.05, cache_time=10, autosave=False, filename='autosave.cpd'):
def start_monitor(self, interval, calculate_from, min_prices=100, max_set_size_diff_pct=90,
overlap_pct=90, max_p_value=0.05, cache_time=10, autosave=False, filename='autosave.cpd'):
"""
Starts monitor to continuously update the coefficient for all symbol pairs in that meet the min_coefficient
threshold.
:param interval: How often to check in seconds
:param from_mins: The number of minutes of tick data to use for calculations
:param calculate_from: The number of minutes of tick data to use for calculation. This can be a single value or
a list. If a list, then calculations will be performed for every from date in list.
:param min_prices: The minimum number of prices that should be used to calculate coefficient. If this threshold
is not met then returned coefficient will be None
:param max_set_size_diff_pct: Correlations will only be calculated if the sizes of the two price data sets are
@@ -228,13 +234,19 @@ class Correlation:
self.__log.debug(f"Starting monitor.")
self.__monitoring = True
# Store the shortest timeframe (which is the largest value) for calculate_from. This will be used when we update
# the coefficient_data dataframe. All calculations for all values specified in calculate_from will be stored in
# coefficient_history, however only the shortest timeframe will be updated in coefficient_data.
self.__shortest_timeframe = min(calculate_from) if isinstance(calculate_from, list) else calculate_from
# Create thread to run monitoring This will call private __monitor method that will run the calculation and
# keep scheduling itself while self.monitoring is True. Store the params. We will need to use these if we have
# to stop and restart the monitor. Note, this happens during calculate
self.__monitoring_params = {'interval': interval, 'from_mins': from_mins,
'min_prices': min_prices, 'max_set_size_diff_pct': max_set_size_diff_pct,
'overlap_pct': overlap_pct, 'max_p_value': max_p_value, 'cache_time': cache_time,
'autosave': autosave, 'filename': filename}
self.__monitoring_params = {'interval': interval, 'calculate_from': calculate_from,
'min_prices': min_prices,
'max_set_size_diff_pct': max_set_size_diff_pct, 'overlap_pct': overlap_pct,
'max_p_value': max_p_value, 'cache_time': cache_time, 'autosave': autosave,
'filename': filename}
thread = threading.Thread(target=self.__monitor, kwargs=self.__monitoring_params)
thread.start()
@@ -306,16 +318,23 @@ class Correlation:
return coefficient
def get_coefficient_history(self, symbol1, symbol2):
def get_coefficient_history(self, symbol1, symbol2, timeframe=None):
"""
Returns the coefficient history for the specified symbol pair calculated during this instance.
Coefficient history does not persist between instances.
:param symbol1:
:param symbol2:
:param timeframe: Only return history for the specified timeframe. If None, this is ignored and all history
records are returned for the specified symbols
:return: dataframe containing history of coefficient data.
"""
history = self.coefficient_history[(self.coefficient_history['Symbol 1'] == symbol1) &
(self.coefficient_history['Symbol 2'] == symbol2)]
# If calculate from was specified, filter on it.
if timeframe is not None:
history = history[(history['Timeframe'] == timeframe)]
return history
def get_ticks(self, symbol, date_from=None, date_to=None, cache_time=0, cache_only=False):
@@ -355,14 +374,15 @@ class Correlation:
self.__log.debug(f"Ticks for {symbol} retrieved from source and cached.")
return ticks
def __monitor(self, interval, from_mins, min_prices=100, max_set_size_diff_pct=90, overlap_pct=90,
max_p_value=0.05, cache_time=10, autosave=False, filename='autosave.cpd'):
def __monitor(self, interval, calculate_from, min_prices=100, max_set_size_diff_pct=90,
overlap_pct=90, max_p_value=0.05, cache_time=10, autosave=False, filename='autosave.cpd'):
"""
The actual monitor method. Private. This should not be called outside of this class. Use start_monitoring and
stop_monitoring.
:param interval: How often to check in seconds
:param from_mins: The number of minutes of tick data to use for calculations
:param calculate_from: The number of minutes of tick data to use for calculation. This can be a single value or
a list. If a list, then calculations will be performed for every from date in list.
:param min_prices: The minimum number of prices that should be used to calculate coefficient. If this threshold
is not met then returned coefficient will be None
:param max_set_size_diff_pct: Correlations will only be calculated if the sizes of the two price data sets are
@@ -382,19 +402,19 @@ class Correlation:
# Only run if monitor is not stopped
if self.__monitoring:
# Update all coefficients
self.__update_all_coefficients(from_mins=from_mins, min_prices=min_prices,
max_set_size_diff_pct=max_set_size_diff_pct, overlap_pct=overlap_pct,
max_p_value=max_p_value, cache_time=cache_time)
self.__update_all_coefficients(calculate_from=calculate_from,
min_prices=min_prices, max_set_size_diff_pct=max_set_size_diff_pct,
overlap_pct=overlap_pct, max_p_value=max_p_value, cache_time=cache_time)
# Autosave
if autosave:
self.save(filename=filename)
# Schedule the timer to run again
params = {'interval': interval, 'from_mins': from_mins, 'min_prices': min_prices,
'max_set_size_diff_pct': max_set_size_diff_pct, 'overlap_pct': overlap_pct,
'max_p_value': max_p_value, "cache_time": cache_time, 'autosave': autosave,
'filename': filename}
params = {'interval': interval, 'calculate_from': calculate_from,
'min_prices': min_prices, 'max_set_size_diff_pct': max_set_size_diff_pct,
'overlap_pct': overlap_pct, 'max_p_value': max_p_value, "cache_time": cache_time,
'autosave': autosave, 'filename': filename}
self.__scheduler.enter(delay=interval, priority=1, action=self.__monitor, kwargs=params)
# Log the stack. Debug stack overflow
@@ -405,13 +425,14 @@ class Correlation:
self.__first_run = False
self.__scheduler.run()
def __update_coefficient(self, symbol1, symbol2, from_mins, min_prices=100, max_set_size_diff_pct=90,
overlap_pct=90, max_p_value=0.05, cache_time=10):
def __update_coefficients(self, symbol1, symbol2, calculate_from, min_prices=100,
max_set_size_diff_pct=90, overlap_pct=90, max_p_value=0.05, cache_time=10):
"""
Updates the coefficient for the specified symbol pair
Updates the long and short coefficients for the specified symbol pair
:param symbol1: Name of symbol to calculate coefficient for.
:param symbol2: Name of symbol to calculate coefficient for.
:param from_mins: The number of minutes of tick data to use for calculations
:param calculate_from: The number of minutes of tick data to use for calculation. This can be a single value or
a list. If a list, then calculations will be performed for every from date in list.
:param min_prices: The minimum number of prices that should be used to calculate coefficient. If this threshold
is not met then returned coefficient will be None
:param max_set_size_diff_pct: Correlations will only be calculated if the sizes of the two price data sets are
@@ -423,61 +444,72 @@ class Correlation:
:return: correlation coefficient, or None if coefficient could not be calculated.
"""
coefficient = None
# Convert calculate from to list of one if only one value is provided
if not isinstance(calculate_from, list):
calculate_from = [calculate_from, ]
# Get dates
# From and to dates for calculations.
# From and to dates for calculations. From should be furthest away if list is provided in calculate_from
timezone = pytz.timezone("Etc/UTC")
date_to = datetime.now(tz=timezone)
date_from = date_to - timedelta(minutes=from_mins)
date_from = date_to - timedelta(minutes=max(calculate_from))
# Get the tick data
# Get the tick data for the longest timeframe calculation. We will extract shorter timeframes from it if list
# was provided in calculate_from to avoid retrieving multiple times.
symbol1ticks = self.get_ticks(symbol=symbol1, date_from=date_from, date_to=date_to, cache_time=cache_time)
symbol2ticks = self.get_ticks(symbol=symbol2, date_from=date_from, date_to=date_to, cache_time=cache_time)
# Resample to 1 sec OHLC, this will help with coefficient calculation ensuring that we dont have more than one
# tick per second and ensuring that times can match. We will need to set the index to time for the resample
# then revert back to a 'time' column. We will then need to remove rows with nan in 'close' price
if symbol1ticks is not None and symbol2ticks is not None and \
len(symbol1ticks.index) > 0 and len(symbol2ticks.index) > 0:
symbol1ticks = symbol1ticks.set_index('time')
symbol2ticks = symbol2ticks.set_index('time')
# Resample to 1 sec OHLC, this will help with coefficient calculation ensuring that we dont have more than
# one tick per second and ensuring that times can match. We will need to set the index to time for the
# resample then revert back to a 'time' column. We will then need to remove rows with nan in 'close' price
if symbol1ticks is not None and symbol2ticks is not None and len(symbol1ticks.index) > 0 and \
len(symbol2ticks.index) > 0:
try:
symbol1prices = symbol1ticks['ask'].resample('1S').ohlc()
symbol2prices = symbol2ticks['ask'].resample('1S').ohlc()
symbol1ticks = symbol1ticks.set_index('time')
symbol2ticks = symbol2ticks.set_index('time')
s1_prices = symbol1ticks['ask'].resample('1S').ohlc()
s2_prices = symbol2ticks['ask'].resample('1S').ohlc()
except RecursionError:
self.__log.warning(f"Coefficient could not be calculated for {symbol1}:{symbol2} as prices could not "
self.__log.warning(f"Coefficient could not be calculated for {symbol1}:{symbol2}. prices could not "
f"be resampled.")
else:
symbol1prices.reset_index(inplace=True)
symbol2prices.reset_index(inplace=True)
symbol1prices = symbol1prices[symbol1prices['close'].notna()]
symbol2prices = symbol2prices[symbol2prices['close'].notna()]
s1_prices.reset_index(inplace=True)
s2_prices.reset_index(inplace=True)
s1_prices = s1_prices[s1_prices['close'].notna()]
s2_prices = s2_prices[s2_prices['close'].notna()]
# Calculate the coefficient
coefficient = self.calculate_coefficient(symbol1_prices=symbol1prices, symbol2_prices=symbol2prices,
min_prices=min_prices,
max_set_size_diff_pct=max_set_size_diff_pct,
overlap_pct=overlap_pct, max_p_value=max_p_value)
# Calculate for all timeframes
for from_mins in calculate_from:
# Get the from date as a datetime64
date_from_subset = pd.Timestamp(date_to - timedelta(minutes=from_mins)).to_datetime64()
self.__log.debug(f"Symbol pair {symbol1}:{symbol2} has a coefficient of {coefficient}.")
else:
coefficient = None
# Get subset of the price data
s1_prices_subset = s1_prices[(s1_prices['time'] >= date_from_subset)]
s2_prices_subset = s2_prices[(s2_prices['time'] >= date_from_subset)]
# Update the coefficient data
if coefficient is not None:
self.__update_coefficient_data(symbol1=symbol1, symbol2=symbol2, coefficient=coefficient,
date_from=date_from, date_to=date_to)
# Calculate the coefficient
coefficient = \
self.calculate_coefficient(symbol1_prices=s1_prices_subset, symbol2_prices=s2_prices_subset,
min_prices=min_prices, max_set_size_diff_pct=max_set_size_diff_pct,
overlap_pct=overlap_pct, max_p_value=max_p_value)
return coefficient
self.__log.debug(f"Symbol pair {symbol1}:{symbol2} has a coefficient of {coefficient} for last "
f"{from_mins} minutes.")
def __update_all_coefficients(self, from_mins, min_prices=100, max_set_size_diff_pct=90, overlap_pct=90,
max_p_value=0.05, cache_time=10):
# Update the coefficient data
if coefficient is not None:
self.__update_coefficient_data(symbol1=symbol1, symbol2=symbol2, coefficient=coefficient,
timeframe=from_mins, date_from=date_from_subset, date_to=date_to)
def __update_all_coefficients(self, calculate_from, min_prices=100, max_set_size_diff_pct=90,
overlap_pct=90, max_p_value=0.05, cache_time=10):
"""
Updates the coefficient for all symbol pairs in that meet the min_coefficient threshold. Symbol pairs that meet
the threshold can be accessed through the filtered_coefficient_data property.
:param from_mins: The number of minutes of tick data to use for calculations
:param calculate_from: The number of minutes of tick data to use for calculation. This can be a single value or
a list. If a list, then calculations will be performed for every from date in list.
:param min_prices: The minimum number of prices that should be used to calculate coefficient. If this threshold
is not met then returned coefficient will be None
:param max_set_size_diff_pct: Correlations will only be calculated if the sizes of the two price data sets are
@@ -493,36 +525,37 @@ class Correlation:
for index, row in self.filtered_coefficient_data.iterrows():
symbol1 = row['Symbol 1']
symbol2 = row['Symbol 2']
self.__update_coefficient(symbol1=symbol1, symbol2=symbol2, from_mins=from_mins,
min_prices=min_prices, max_set_size_diff_pct=max_set_size_diff_pct,
overlap_pct=overlap_pct, max_p_value=max_p_value, cache_time=cache_time)
self.__update_coefficients(symbol1=symbol1, symbol2=symbol2, calculate_from=calculate_from,
min_prices=min_prices, max_set_size_diff_pct=max_set_size_diff_pct,
overlap_pct=overlap_pct, max_p_value=max_p_value, cache_time=cache_time)
def __reset_coefficient_data(self):
"""
Clears coefficient data and history.
:return:
"""
# Create dataframe for coefficient data
# Create dataframes for coefficient data.
coefficient_data_columns = ['Symbol 1', 'Symbol 2', 'Base Coefficient', 'UTC Date From', 'UTC Date To',
'Timeframe', 'Last Check', 'Last Coefficient']
self.coefficient_data = pd.DataFrame(columns=coefficient_data_columns)
# Create dataframe for coefficient history
coefficient_history_columns = ['Symbol 1', 'Symbol 2', 'Coefficient', 'UTC Date From', 'UTC Date To']
# Create dataframes for coefficient history.
coefficient_history_columns = ['Symbol 1', 'Symbol 2', 'Coefficient', 'Timeframe', 'Date From', 'Date To']
self.coefficient_history = pd.DataFrame(columns=coefficient_history_columns)
# Clear price data and tick data
self.__price_data = None
self.__monitor_tick_data = {}
def __update_coefficient_data(self, symbol1, symbol2, coefficient, date_from, date_to):
def __update_coefficient_data(self, symbol1, symbol2, coefficient, timeframe, date_from, date_to):
"""
Updates the coefficient data with the latest coefficient and adds to coefficient history.
:param symbol1:
:param symbol2:
:param coefficient:
:param date_from:
:param date_to:
:param coefficient: The coefficient calculated
:param timeframe: The number of minutes of price data used to calculate the coefficient
:param date_from: The date from for which the coefficient was calculated
:param date_to: The date from for which the coefficient was calculated
:return:
"""
@@ -531,14 +564,17 @@ class Correlation:
# Update data if we have a coefficient and add to history
if coefficient is not None:
self.coefficient_data.loc[(self.coefficient_data['Symbol 1'] == symbol1) &
(self.coefficient_data['Symbol 2'] == symbol2),
'Last Check'] = now
# The coefficient data table is only updated for the shortest calculation timeframe.
if timeframe == self.__shortest_timeframe:
self.coefficient_data.loc[(self.coefficient_data['Symbol 1'] == symbol1) &
(self.coefficient_data['Symbol 2'] == symbol2),
'Last Check'] = now
self.coefficient_data.loc[(self.coefficient_data['Symbol 1'] == symbol1) &
(self.coefficient_data['Symbol 2'] == symbol2),
'Last Coefficient'] = coefficient
self.coefficient_data.loc[(self.coefficient_data['Symbol 1'] == symbol1) &
(self.coefficient_data['Symbol 2'] == symbol2),
'Last Coefficient'] = coefficient
# However the history data is always updated
row = pd.DataFrame(columns=self.coefficient_history.columns,
data=[[symbol1, symbol2, coefficient, date_from, date_to]])
data=[[symbol1, symbol2, coefficient, timeframe, date_from, date_to]])
self.coefficient_history = self.coefficient_history.append(row)
+46 -25
View File
@@ -284,7 +284,8 @@ class MonitorFrame(wx.Frame):
filename = self.__opened_filename if self.__opened_filename is not None else 'autosave.cpd'
self.__cor.start_monitor(interval=self.__config.get('monitor.interval'),
from_mins=self.__config.get('monitor.from.minutes'),
calculate_from=[self.__config.get('monitor.calculate_from.long.minutes'),
self.__config.get('monitor.calculate_from.short.minutes')],
min_prices=self.__config.get('monitor.min_prices'),
max_set_size_diff_pct=self.__config.get('monitor.max_set_size_diff_pct'),
overlap_pct=self.__config.get('monitor.overlap_pct'),
@@ -329,7 +330,7 @@ class MonitorFrame(wx.Frame):
reload_correlations = True
if setting.startswith('logging.'):
reload_logger = True
if setting.startswith('monitor.from.'):
if setting.startswith('monitor.calculate_from'):
reload_graph = True
# Now perform the actions
@@ -337,7 +338,8 @@ class MonitorFrame(wx.Frame):
self.__log.info("Settings updated. Reloading monitoring timer.")
self.__cor.stop_monitor()
self.__cor.start_monitor(interval=self.__config.get('monitor.interval'),
from_mins=self.__config.get('monitor.from.minutes'),
calculate_from=[self.__config.get('monitor.calculate_from.long.minutes'),
self.__config.get('monitor.calculate_from.short.minutes')],
min_prices=self.__config.get('monitor.min_prices'),
max_set_size_diff_pct=self.__config.get('monitor.max_set_size_diff_pct'),
overlap_pct=self.__config.get('monitor.overlap_pct'),
@@ -418,14 +420,16 @@ class MonitorFrame(wx.Frame):
symbol_2_price_data = self.__cor.get_price_data(symbol2)
symbol_1_ticks = self.__cor.get_ticks(symbol1, cache_only=True)
symbol_2_ticks = self.__cor.get_ticks(symbol2, cache_only=True)
history_data = self.__cor.get_coefficient_history(symbol1, symbol2)
times = history_data['UTC Date To']
coefficients = history_data['Coefficient']
history_data_short = \
self.__cor.get_coefficient_history(symbol1, symbol2,
self.__config.get('monitor.calculate_from.short.minutes'))
history_data_long = \
self.__cor.get_coefficient_history(symbol1, symbol2,
self.__config.get('monitor.calculate_from.long.minutes'))
# Display if we have any data
self.__log.debug(f"Refreshing history graph {symbol1}:{symbol2}.")
self.__graph.draw(times=times, coefficients=coefficients, prices=[symbol_1_price_data, symbol_2_price_data],
ticks=[symbol_1_ticks, symbol_2_ticks], symbols=[symbol1, symbol2])
self.__graph.draw(prices=[symbol_1_price_data, symbol_2_price_data], ticks=[symbol_1_ticks, symbol_2_ticks],
history=[history_data_short, history_data_long], symbols=[symbol1, symbol2])
# Un-hide and layout if hidden
if not self.__graph.IsShown():
@@ -434,7 +438,7 @@ class MonitorFrame(wx.Frame):
def __timer_event(self, event):
"""
Called on timer event. Refreshes grid and updatates selected graph.
Called on timer event. Refreshes grid and updates selected graph.
:return:
"""
self.refresh_grid()
@@ -486,7 +490,7 @@ class DataTable(wx.grid.GridTableBase):
# If column is last coefficient, get value and check against threshold. Highlight if diverged.
threshold = Config().get('monitor.divergence_threshold')
if col == MonitorFrame.COLUMN_LAST_COEFFICIENT:
if col in [MonitorFrame.COLUMN_LAST_COEFFICIENT]:
value = self.GetValue(row, col)
if value != "":
value = float(value)
@@ -531,26 +535,39 @@ class GraphPanel(wx.Panel):
self.__axes = None
self.__fig = None
def draw(self, times, coefficients, prices=None, symbols=None, ticks=None):
def draw(self, prices=None, ticks=None, history=None, symbols=None):
"""
Plot the correlations.
:param times: Series of time values for x axis for coefficient history chart
:param coefficients: Series of coefficients values for y axis of coefficient history chart
:param prices: Price data used to calculate base coefficient. List [Symbol1 Price Data, Symbol 2 Price Data]
:param symbols: Symbols. List [Symbol1, Symbol2]
:param ticks: Ticks used to calculate last coefficient. List [Symbol1, Symbol2]
:param history: Coefficient history data. List of data for one or more timeframes.
:param symbols: Symbols. List [Symbol1, Symbol2]
:return:
"""
# Get all plots for history. History can contain multiple plots for different timeframes. They will all be
# plotted on the same chart.
times = []
coefficients = []
for hist in history:
times.append(hist['Date To'])
coefficients.append(hist['Coefficient'])
# Clear. We will need to redraw
for ax in self.__axes:
ax.clear()
if symbols is not None and len(symbols) == 2:
# Axis ranges
price_chart_date_range = [min(min(prices[0]['time']), min(prices[1]['time'])),
max(max(prices[0]['time']), max(prices[1]['time']))]
tick_chart_date_range = [min(min(ticks[0]['time']), min(ticks[1]['time'])),
max(max(ticks[0]['time']), max(ticks[1]['time']))]
default_range = [datetime.now() - timedelta(days=1), datetime.now()]
price_chart_date_range = default_range # We need a default here if no data is available
if prices is not None and len(prices) == 0 and prices[0] is not None and prices[1] is not None:
price_chart_date_range = [min(min(prices[0]['time']), min(prices[1]['time'])),
max(max(prices[0]['time']), max(prices[1]['time']))]
tick_chart_date_range = default_range # We need a default here if no data is available
if ticks is not None and len(ticks) == 0 and ticks[0] is not None and ticks[1] is not None:
tick_chart_date_range = [min(min(ticks[0]['time']), min(ticks[1]['time'])),
max(max(ticks[0]['time']), max(ticks[1]['time']))]
# Chart config
titles = [f"Base Coefficient Price Data for {symbols[0]}", f"Base Coefficient Price Data for {symbols[1]}",
@@ -560,7 +577,7 @@ class GraphPanel(wx.Panel):
ylims = [None, None, None, None, [-1, 1]]
xlabels = [None, None, None, None, None]
ylabels = ['Price', 'Price', 'Price', 'Price', 'Coefficient']
tick_labels = [[], prices[1]['time'], [], ticks[1]['time'], times]
tick_labels = [[], prices[1]['time'], [], ticks[1]['time'], times[0]]
mtick_fmts = [None, self.__tick_fmt_date, None, self.__tick_fmt_time, self.__tick_fmt_time]
mtick_rot = [0, 45, 0, 45, 45]
xdata = [prices[0]['time'], prices[1]['time'], ticks[0]['time'], ticks[1]['time'], times]
@@ -591,11 +608,15 @@ class GraphPanel(wx.Panel):
self.__axes[index].spines["top"].set_visible(False)
self.__axes[index].spines["right"].set_visible(False)
# Plot
if types[index] == 'plot':
self.__axes[index].plot(xdata[index], ydata[index])
elif types[index] == 'scatter':
self.__axes[index].scatter(xdata[index], ydata[index], s=1)
# Plot. There may be more than one set of data or each chart. Convert single data to list, then loop
xdata_list = xdata[index] if isinstance(xdata[index], list) else [xdata[index], ]
ydata_list = ydata[index] if isinstance(ydata[index], list) else [ydata[index], ]
for data_index in range(0, len(xdata_list)):
if types[index] == 'plot':
self.__axes[index].plot(xdata_list[data_index], ydata_list[data_index])
elif types[index] == 'scatter':
self.__axes[index].scatter(xdata_list[data_index], ydata_list[data_index], s=1)
# Layout with padding between charts
self.__fig.tight_layout(pad=0.5)
+13 -9
View File
@@ -198,11 +198,11 @@ class TestCorrelation(unittest.TestCase):
# Patch it in
mock.copy_ticks_range.side_effect = [tick_data_s1, tick_data_s3, tick_data_s4]
# Start the monitor. Run every second. Use ~10 seconds of data. Were not testing the overlap and price data
# quality metrics here as that is set elsewhere so these can be set to not take effect. Set cache level high
# and don't use autosave. Timer runs in a separate thread so test can continue after it has started.
cor.start_monitor(interval=1, from_mins=0.66, min_prices=0, max_set_size_diff_pct=0, overlap_pct=0,
max_p_value=1, cache_time=100, autosave=False)
# Start the monitor. Run every second. Use ~10 and ~5 seconds of data. Were not testing the overlap and price
# data quality metrics here as that is set elsewhere so these can be set to not take effect. Set cache level
# high and don't use autosave. Timer runs in a separate thread so test can continue after it has started.
cor.start_monitor(interval=1, calculate_from=[0.66, 0.33], min_prices=0, max_set_size_diff_pct=0,
overlap_pct=0, max_p_value=1, cache_time=100, autosave=False)
# Wait 2 seconds so timer runs twice
time.sleep(2)
@@ -210,8 +210,12 @@ class TestCorrelation(unittest.TestCase):
# Stop the monitor
cor.stop_monitor()
# We should have 2 coefficients calculated for each symbol pair
self.assertEqual(len(cor.coefficient_history.index), 6)
# We should have 2 coefficients calculated for each symbol pair for each date_from value, so 12 in total.
self.assertEqual(len(cor.coefficient_history.index), 12)
# We should have 2 coefficients calculated for a single symbol pair and timeframe
self.assertEqual(len(cor.get_coefficient_history('SYMBOL1', 'SYMBOL2', 0.66)), 2,
"We should have 2 history records for SYMBOL1:SYMBOL2 using the 0.66 min timeframe.")
@patch('mt5_correlation.mt5.MetaTrader5')
def test_load_and_save(self, mock):
@@ -246,8 +250,8 @@ class TestCorrelation(unittest.TestCase):
# Start monitor and run for a seconds with a 1 second interval to produce some coefficient history. Then stop
# the monitor
cor.start_monitor(interval=1, from_mins=0.66, min_prices=0, max_set_size_diff_pct=0, overlap_pct=0,
max_p_value=1, cache_time=100, autosave=False)
cor.start_monitor(interval=1, calculate_from=0.66, min_prices=0, max_set_size_diff_pct=0,
overlap_pct=0, max_p_value=1, cache_time=100, autosave=False)
time.sleep(2)
cor.stop_monitor()