| Overall Statistics |
|
Total Trades 355 Average Win 0.00% Average Loss 0.00% Compounding Annual Return 1.858% Drawdown 0.000% Expectancy 0.262 Net Profit 0.197% Sharpe Ratio 5.086 Probabilistic Sharpe Ratio 90.982% Loss Rate 58% Win Rate 42% Profit-Loss Ratio 1.99 Alpha 0.007 Beta 0.021 Annual Standard Deviation 0.003 Annual Variance 0 Information Ratio -4.828 Tracking Error 0.075 Treynor Ratio 0.701 Total Fees $355.00 Estimated Strategy Capacity $220000.00 Lowest Capacity Asset FXF TJSL8DEZVWBP |
import datetime
import time
#from typeguard import typechecked
import tensorflow as tf
from tensorflow.keras.callbacks import Callback
from json import JSONEncoder
import numpy as np
import pandas as pd
import random
#@tf.keras.utils.register_keras_serializable(package="Addons")
# should i be using this class instead
# https://stackoverflow.com/questions/62031302/how-to-apply-monte-carlo-dropout-in-tensorflow-for-an-lstm-if-batch-normalizat
class MCLSTM(tf.keras.layers.LSTM):
def __init__(self, units, **kwargs):
super(MCLSTM, self).__init__(units, **kwargs)
def call(self, inputs, mask=None, training=None, initial_state=None):
return super(MCLSTM, self).call(
inputs,
mask=mask,
training=True,
initial_state=initial_state,
)
class TimeStopping(Callback):
"""Stop training when a specified amount of time has passed.
Args:
seconds: maximum amount of time before stopping.
Defaults to 86400 (1 day).
verbose: verbosity mode. Defaults to 0.
from utils import TimeStopping
time_stopping_callback = TimeStopping(seconds=5, verbose=1)
time_stopping_callback
"""
# @typechecked
def __init__(self, seconds: int = 86400, verbose: int = 0):
super().__init__()
self.seconds = seconds
self.verbose = verbose
self.stopped_epoch = None
def on_train_begin(self, logs=None):
self.stopping_time = time.time() + self.seconds
def on_epoch_end(self, epoch, logs={}):
if time.time() >= self.stopping_time:
self.model.stop_training = True
self.stopped_epoch = epoch
def on_train_end(self, logs=None):
if self.stopped_epoch is not None and self.verbose > 0:
formatted_time = datetime.timedelta(seconds=self.seconds)
msg = "Timed stopping at epoch {} after training for {}".format(
self.stopped_epoch + 1, formatted_time
)
print(msg)
def get_config(self):
config = {
"seconds": self.seconds,
"verbose": self.verbose,
}
base_config = super().get_config()
return {**base_config, **config}
# should i be inheriting the qc algorithm in this class...
# https://www.quantconnect.com/forum/discussion/12160/keltner-channel-universe/p1
# should i move the trading object over here as well so all over here..
class generateSimData(object):
def __init__(self, dataset_train, y_predict, y_predict_pnorm, y_predict_volnorm, sim_strat):
self.y_predict = y_predict
self.y_predict_pnorm = y_predict_pnorm
self.y_predict_volnorm = y_predict_volnorm
self.sim_strat = sim_strat
self.dataset_train = dataset_train
@property
def Data(self):
dataset_yproba_reshape = self.y_predict
dataset_yproba_reshape_pnorm = self.y_predict_pnorm
dataset_yproba_reshape_volnorm = self.y_predict_volnorm
len_yproba = len(dataset_yproba_reshape)
yproba_max = []
yproba_min = []
yproba_open = []
yproba_close = [] # note sure which of these should be static open or close so will do both and can choose
yproba_vol = []
# or time could be the value that is moving as well..
for x in range(0, len_yproba, 10):
# x_old_plus1 = x + 1
x_plus10 = x + 10
if(x_plus10 != len_yproba):
temp_yproba_ = dataset_yproba_reshape[x:x_plus10]
temp_yproba_pnorm = dataset_yproba_reshape_pnorm[x:x_plus10]
temp_yproba_volnorm = dataset_yproba_reshape_volnorm[x:x_plus10]
temp_yproba = temp_yproba_pnorm
temp_yproba_open = temp_yproba[0]
temp_yproba_close = temp_yproba[-1]
temp_yproba_max = np.amax(temp_yproba,axis=0,keepdims=False)
temp_yproba_min = np.amin(temp_yproba,axis=0,keepdims=False)
# temp_yproba_vol =
temp_yproba_max = np.repeat(temp_yproba_max, 10, axis=0)
temp_yproba_min = np.repeat(temp_yproba_min, 10, axis=0)
temp_yproba_open = np.repeat(temp_yproba_open, 10, axis=0)
temp_yproba_close = np.repeat(temp_yproba_close, 10, axis=0)
yproba_max.append(temp_yproba_max)
yproba_min.append(temp_yproba_min)
yproba_open.append(temp_yproba_open)
yproba_close.append(temp_yproba_close)
# do mean of these values
# can improve logic better by understanding effect of close/open as currently just some randomness involved in this
yproba_max_simmax = np.mean(temp_yproba_max,axis=0,keepdims=False)
yproba_min_simmin = np.mean(temp_yproba_min,axis=0,keepdims=False)
yproba_open_simopen = np.mean(temp_yproba_open,axis=0,keepdims=False)
yproba_close_simclose = np.mean(temp_yproba_close,axis=0,keepdims=False)
yproba_price_simprice = np.mean(temp_yproba_pnorm,axis=0,keepdims=False)
yproba_price_simvolume = np.mean(temp_yproba_volnorm,axis=0,keepdims=False)
dataset_simlive = pd.DataFrame()
dataset_simlive['Time'] = yproba_close_simclose
dataset_simlive['Open'] = yproba_open_simopen
dataset_simlive['Close'] = yproba_price_simprice[0]
dataset_simlive['High'] = yproba_max_simmax
dataset_simlive['Low'] = yproba_min_simmin
# dataset_simlive['Volume'] = 0
dataset_simlive['Volume'] = yproba_price_simvolume[0]
self.dataset_simlive = dataset_simlive
dataset_live_agg_arr = []
# for i in range(0,10000):
for i in range(0,5000):
# for i in range(0,100):
# for i in range(0,10):
# for i in range(0,1):
sample_shape = self.dataset_train.shape[1] - 1
p1 =random.randint(0,sample_shape)
p2 =random.randint(0,sample_shape)
p3 =random.randint(0,sample_shape)
random_dim = random.randint(1,len(yproba_max)-1)
dataset_live = pd.DataFrame()
df_yproba_close = pd.DataFrame(yproba_close[random_dim])
df_yproba_open = pd.DataFrame(yproba_open[random_dim])
df_yproba_high = pd.DataFrame(yproba_max[random_dim])
df_yproba_low = pd.DataFrame(yproba_min[random_dim])
# changed this recently should i have stuck to previous implementation...
# if(self.sim_strat == 'average'):
# dataset_live['Time'] = (df_yproba_close[p3] + df_yproba_close[p2] + df_yproba_close[p1]) / 3
# dataset_live['Open'] = (df_yproba_open[p3] + df_yproba_open[p2] + df_yproba_open[p1]) / 3
# dataset_live['High'] = (df_yproba_high[p3] + df_yproba_high[p2] + df_yproba_high[p1]) / 3
# dataset_live['Low'] = (df_yproba_low[p3] + df_yproba_low[p2] + df_yproba_low[p1]) / 3
# else:
dataset_live['Time'] = df_yproba_close[p1]
dataset_live['Open'] = df_yproba_open[p1]
dataset_live['High'] = df_yproba_high[p1]
dataset_live['Low'] = df_yproba_low[p1]
#df_yproba = pd.DataFrame(dataset_yproba.reshape(10000,13))
# change this to a for loop save 30to50 lines
price1 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price2 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price3 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price4 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price5 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price6 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price7 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price8 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price9 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price10 = random.randint(1,len(dataset_yproba_reshape_pnorm)-1)
price = []
volume = []
if(self.sim_strat == 'average'):
# self.Debug(str('using averages of simmed data'))
price.append((dataset_yproba_reshape_pnorm[price1][0][p3] + dataset_yproba_reshape_pnorm[price1][0][p2] + dataset_yproba_reshape_pnorm[price1][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price2][0][p3] + dataset_yproba_reshape_pnorm[price2][0][p2] + dataset_yproba_reshape_pnorm[price2][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price3][0][p3] + dataset_yproba_reshape_pnorm[price3][0][p2] + dataset_yproba_reshape_pnorm[price3][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price4][0][p3] + dataset_yproba_reshape_pnorm[price4][0][p2] + dataset_yproba_reshape_pnorm[price4][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price5][0][p3] + dataset_yproba_reshape_pnorm[price5][0][p2] + dataset_yproba_reshape_pnorm[price5][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price6][0][p3] + dataset_yproba_reshape_pnorm[price6][0][p2] + dataset_yproba_reshape_pnorm[price6][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price7][0][p3] + dataset_yproba_reshape_pnorm[price7][0][p2] + dataset_yproba_reshape_pnorm[price7][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price8][0][p3] + dataset_yproba_reshape_pnorm[price8][0][p2] + dataset_yproba_reshape_pnorm[price8][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price9][0][p3] + dataset_yproba_reshape_pnorm[price9][0][p2] + dataset_yproba_reshape_pnorm[price9][0][p1])/3)
price.append((dataset_yproba_reshape_pnorm[price10][0][p3] + dataset_yproba_reshape_pnorm[price10][0][p2] + dataset_yproba_reshape_pnorm[price10][0][p1])/3)
else:
# self.Debug(str('using singles of simmed data'))
price.append(dataset_yproba_reshape_pnorm[price1][0][p1])
price.append(dataset_yproba_reshape_pnorm[price2][0][p1])
price.append(dataset_yproba_reshape_pnorm[price3][0][p1])
price.append(dataset_yproba_reshape_pnorm[price4][0][p1])
price.append(dataset_yproba_reshape_pnorm[price5][0][p1])
price.append(dataset_yproba_reshape_pnorm[price6][0][p1])
price.append(dataset_yproba_reshape_pnorm[price7][0][p1])
price.append(dataset_yproba_reshape_pnorm[price8][0][p1])
price.append(dataset_yproba_reshape_pnorm[price9][0][p1])
price.append(dataset_yproba_reshape_pnorm[price10][0][p1])
if(self.sim_strat == 'average'):
# self.Debug(str('using averages of simmed data'))
volume.append((dataset_yproba_reshape_volnorm[price1][0][p3] + dataset_yproba_reshape_volnorm[price1][0][p2] + dataset_yproba_reshape_volnorm[price1][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price2][0][p3] + dataset_yproba_reshape_volnorm[price2][0][p2] + dataset_yproba_reshape_volnorm[price2][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price3][0][p3] + dataset_yproba_reshape_volnorm[price3][0][p2] + dataset_yproba_reshape_volnorm[price3][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price4][0][p3] + dataset_yproba_reshape_volnorm[price4][0][p2] + dataset_yproba_reshape_volnorm[price4][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price5][0][p3] + dataset_yproba_reshape_volnorm[price5][0][p2] + dataset_yproba_reshape_volnorm[price5][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price6][0][p3] + dataset_yproba_reshape_volnorm[price6][0][p2] + dataset_yproba_reshape_volnorm[price6][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price7][0][p3] + dataset_yproba_reshape_volnorm[price7][0][p2] + dataset_yproba_reshape_volnorm[price7][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price8][0][p3] + dataset_yproba_reshape_volnorm[price8][0][p2] + dataset_yproba_reshape_volnorm[price8][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price9][0][p3] + dataset_yproba_reshape_volnorm[price9][0][p2] + dataset_yproba_reshape_volnorm[price9][0][p1])/3)
volume.append((dataset_yproba_reshape_volnorm[price10][0][p3] + dataset_yproba_reshape_volnorm[price10][0][p2] + dataset_yproba_reshape_volnorm[price10][0][p1])/3)
# not sure which is best way to do this
# can either unnorm yproba before simming or not
# currently unnorming after simming here, which means i include some of interaction effect in this simmed value..
dataset_live['Close'] = price
dataset_live['Volume'] = volume
dataset_live_agg_arr.append(dataset_live)
# end logic
self.dataset_live_temp = pd.concat(dataset_live_agg_arr)
return self.dataset_live_temp
@property
def TradeData(self):
return self.dataset_simlive
class SymbolDataPNorm(object):
def __init__(self, algo, symbol, barPeriod, indicPeriod, windowSize, resolutionTime):
self.Symbol = symbol
#self.warmup = warmup
self.BarPeriod = barPeriod
self.Bars = RollingWindow[IBaseDataBar](windowSize)
self.consolidator = TradeBarConsolidator(barPeriod)
self.consolidator.DataConsolidated += algo.OnDataConsolidated
algo.SubscriptionManager.AddConsolidator(symbol, self.consolidator)
self.Volume = None
self.SMA_ = SimpleMovingAverage(algo.CreateIndicatorName(symbol, "SMA" + str(1), resolutionTime), 1)
sma_lookback = 60
self.HistoricalSMA_ = RollingWindow[float](sma_lookback)
self.SMA = SimpleMovingAverage(algo.CreateIndicatorName(symbol, "SMA" + str(indicPeriod), resolutionTime), indicPeriod)
sma_lookback = 60
self.HistoricalSMA = RollingWindow[float](sma_lookback)
self.STD = StandardDeviation(algo.CreateIndicatorName(symbol, "STD" + str(indicPeriod), resolutionTime), indicPeriod)
std_lookback = 60
self.HistoricalSTD = RollingWindow[float](std_lookback)
self.price_minus_sma = IndicatorExtensions.Minus(self.SMA_, self.SMA)
price_minus_sma = 60
self.HistoricalPriceMinusSMA = RollingWindow[float](price_minus_sma)
self.PNorm = IndicatorExtensions.Over(self.price_minus_sma, self.STD)
pnorm_lookback = 60
self.HistoricalPNorm = RollingWindow[float](pnorm_lookback)
volsma_lookback = 60
# epsilon = 0.000000001
# self.VOLSMA = IndicatorExtensions.Plus(SimpleMovingAverage(algo.CreateIndicatorName(symbol, "VOLSMA" + str(indicPeriod), resolutionTime), 1),epsilon)
self.VOLSMA_ = SimpleMovingAverage(algo.CreateIndicatorName(symbol, "VOLSMA" + str(1), resolutionTime), 1)
self.HistoricalVOLSMA_ = RollingWindow[float](volsma_lookback)
# self.VOLSMA = SimpleMovingAverage(algo.CreateIndicatorName(symbol, "VOLSMA" + str(indicPeriod), resolutionTime), 1)
self.VOLSMA = SimpleMovingAverage(algo.CreateIndicatorName(symbol, "VOLSMA" + str(indicPeriod), resolutionTime), 3)
self.HistoricalVOLSMA = RollingWindow[float](volsma_lookback)
self.VOLSTD = StandardDeviation(algo.CreateIndicatorName(symbol, "VOLSTD" + str(indicPeriod), resolutionTime), 3)
self.HistoricalVOLSTD = RollingWindow[float](volsma_lookback)
# self.VOLMIN = IndicatorExtensions.MIN(self.VOLSMA_, indicPeriod)
self.VOLMIN = IndicatorExtensions.MIN(self.VOLSMA, indicPeriod)
self.HistoricalVOLMIN = RollingWindow[float](volsma_lookback)
# self.VOLMAX = IndicatorExtensions.MAX(self.VOLSMA_, indicPeriod)
self.VOLMAX = IndicatorExtensions.MAX(self.VOLSMA, indicPeriod)
self.HistoricalVOLMAX = RollingWindow[float](volsma_lookback)
# (x-min(x))/(max(x)-min(x))
# self.volminusmin = IndicatorExtensions.Minus(self.VOLSMA_, self.VOLMIN)
self.volminusmin = IndicatorExtensions.Minus(self.VOLSMA, self.VOLMIN)
self.HistoricalVOLMINUSMIN = RollingWindow[float](volsma_lookback)
self.maxvolminusminvol = IndicatorExtensions.Minus(self.VOLMAX, self.VOLMIN)
self.HistoricalMAXVOLMINUSMINVOL = RollingWindow[float](volsma_lookback)
# self.volnorm = IndicatorExtensions.Over(self.volminusmin, self.maxvolminusminvol)
# self.HistoricalVOLNORM = RollingWindow[float](volsma_lookback)
self.volminussma = IndicatorExtensions.Minus(self.VOLSMA_, self.VOLSMA)
self.HistoricalVOLMINUSSMA = RollingWindow[float](volsma_lookback)
self.volnorm = IndicatorExtensions.Over(self.volminussma, self.VOLSTD)
self.HistoricalVOLNORM = RollingWindow[float](volsma_lookback)
# self.RegisterIndicator(self.Symbol, self.HistoricalVOLNORM, self.consolidator)
# RegisterIndicator # https://www.quantconnect.com/docs/algorithm-reference/indicators
# .RegisterIndicator(security.Symbol, self.HistoricalVOLNORM, self.consolidator)
#if(self.warmup == True):
'''
history = algo.History(self.Symbol,60,Resolution.Hour)
if not history.empty:
for time, row in history.loc[symbol].iterrows():
self.PNorm.Update(time,row['close'])
self.STD.Update(time,row['close'])
self.SMA.Update(time,row['close'])
self.SMA_.Update(time,row['close'])
self.VOLSMA.Update(time,row['close'])
self.VOLSTD.Update(time,row['close'])
self.VOLMAX.Update(time,row['close'])
self.VOLMAX.Update(time,row['close'])
self.VOLMIN.Update(time,row['close'])
self.volnorm.Update(time,row['close'])
'''
# Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
# @property
def IsReady(self):
# return self.Bars.IsReady and self.PNorm.IsReady and self.HistoricalPNorm.IsReady and self.STD.IsReady and self.HistoricalSTD.IsReady and self.SMA.IsReady and self.HistoricalSMA.IsReady
return self.Bars.IsReady and self.PNorm.IsReady and self.HistoricalPNorm.IsReady
return self.Bars.IsReady and self.STD.IsReady and self.HistoricalSTD.IsReady
return self.Bars.IsReady and self.SMA.IsReady and self.HistoricalSMA.IsReady
return self.Bars.IsReady and self.SMA_.IsReady and self.HistoricalSMA_.IsReady
# return self.Bars.IsReady and self.price_minus_sma.IsReady and self.HistoricalPriceMinusSMA.IsReady
return self.Bars.IsReady and self.VOLSMA.IsReady and self.HistoricalVOLSMA.IsReady
# return self.Bars.IsReady and self.VOLSMA_.IsReady and self.HistoricalVOLSMA_.IsReady
return self.Bars.IsReady and self.VOLSTD.IsReady and self.HistoricalVOLSTD.IsReady
return self.Bars.IsReady and self.VOLMAX.IsReady and self.HistoricalVOLMIN.IsReady
return self.Bars.IsReady and self.VOLMIN.IsReady and self.HistoricalVOLMAX.IsReady
# return self.Bars.IsReady and self.volminusmin.IsReady and self.HistoricalVOLMINUSMIN.IsReady
# return self.Bars.IsReady and self.maxvolminusminvol.IsReady and self.HistoricalMAXVOLMINUSMINVOL.IsReady
return self.Bars.IsReady and self.volnorm.IsReady and self.HistoricalVOLNORM.IsReady
# Returns true if the most recent trade bar time matches the current time minus the bar's period, this
# indicates that update was just called on this instance
def WasJustUpdated(self, current):
return self.Bars.Count > 0 and self.Bars[0].Time == current - self.BarPeriod
# could move this to library as use it enough
class EncodeNumpyArray(JSONEncoder):
def default(self, obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
return JSONEncoder.default(self, obj)
# build out a class for this if you want it...
#def get_mem_usage(self):
# process = psutil.Process(os.getpid())
# return process.memory_info() from QuantConnect.Orders import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Algorithm.Framework.Selection import *
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Algorithm.Framework")
from QuantConnect.Orders.Fees import ConstantFeeModel
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Indicators import *
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
from System import *
from QuantConnect import *
from QuantConnect.Data.Consolidators import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import OrderStatus
from QuantConnect.Algorithm import QCAlgorithm
from datetime import timedelta, datetime
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation, Dropout, GaussianDropout, BatchNormalization, Flatten, LSTM
from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.utils import serialize_keras_object
import tensorflow as tf
from tensorflow.keras.constraints import max_norm, min_max_norm
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator
import numpy as np
from numpy import hstack
import pandas as pd
import json
import time
from typing import Optional, Union
import heapq
from itertools import count
import random
from System.Drawing import Color
from kerasutils import TimeStopping, SymbolDataPNorm, EncodeNumpyArray, generateSimData
from stocksenv import Actions, Positions, StocksEnv, TradingEnv
import cloudpickle
import stable_baselines
from stable_baselines.common.vec_env import DummyVecEnv, SubprocVecEnv
from stable_baselines.deepq.policies import MlpPolicy
from stable_baselines import DQN, A2C
from stable_baselines.bench import Monitor
import os
from tensorflow.python.client import device_lib
import gc
import timeit
import psutil
class MultipleSymbolConsolidationAlgorithm(QCAlgorithm):
def Initialize(self):
# since running out of money and haven't optimized trade strat, want to continue to see hwo this performs in limitless mode.
# self.SetCash(10000)
self.SetCash(10000000)
self.model = None
self.model_rl = None
self.modelIsTraining = False
self.quick_mape = 'none'
self.dataset_live_append = []
self.dataset_simlive_append = []
self.sim_strat = 'average' # combine in avg's of three to mask simulated data further e.g. avgs of 3 combined simmed variables
# self.sim_strat = 'single' # just a lot of individual single variables simmed so more likely to hit same data simmed again so less fluctuations,
self.SetStartDate(2021, 8, 1) # Set Start Date\
self.DataPNorm = {}
self.Indicators = {}
self.HistoricalIndicators = {}
#self.bars_store = 10080
self.train_range = 50 # set how many bars/minutes to lookback
self.dataset_train = []
self.generator_length = 3
# self.AddAlpha(MultipleSymbolConsolidationAlgorithm)
# self.dataset_train = []
# self.dataset_test = []
# Plot Chart
stockPlot = Chart('Trade Plot')
stockPlot.AddSeries(Series('Avg Predict Price', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Avg Actual Price', SeriesType.Line, 0))
stockPlot.AddSeries(Series('MAE Error', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example1 Predicted', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example1 Actual', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example2 Predicted', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example2 Actual', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Delta Negative', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Delta Positive', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example D--', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example D++', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Model Shape', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example1 Predicted VOL', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Example1 Actual VOL', SeriesType.Line, 0))
self.AddChart(stockPlot)
self.easy_symbols = ['SPY','XLI','SHY','GLD','SLV','XLU','XLI','FXF','FXA','DBB','IGE','RINF','TIP','UUP','MSFT','AAPL','FB','AMZN','TSLA','NVDA','GOOG','AMD']
self.key = str(len(self.easy_symbols)) # e.g. '13' or '21' or '103'
self.symbols = [Symbol.Create(x, SecurityType.Equity, Market.USA) for x in self.easy_symbols]
self.num_macros = len(self.symbols)
self.SetUniverseSelection(ManualUniverseSelectionModel(self.symbols))
# self.UniverseSettings.Resolution = Resolution.Minute
self.UniverseSettings.Resolution = Resolution.Hour
self.UniverseSettings.SetDataNormalizationMode = DataNormalizationMode.Raw
self.UniverseSettings.Leverage = 1
self.SetBrokerageModel(AlphaStreamsBrokerageModel())
self.SetExecution(ImmediateExecutionModel())
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
# self.SetUniverseSelection(LiquidETFUniverse())
self.Train(self.DateRules.Every(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday), self.TimeRules.At(3,0), self.CreateModel)
self.energy = LiquidETFUniverse.Energy
self.metals = LiquidETFUniverse.Metals
self.technology = LiquidETFUniverse.Technology
self.treasuries = LiquidETFUniverse.Treasuries
self.volatility = LiquidETFUniverse.Volatility
self.sp500Sectors = LiquidETFUniverse.SP500Sectors
self.AddEquity("SPY")
# every 6 months delete the model
# self.Schedule.On(self.DateRules.On(2021, 8, 2), self.TimeRules.At(13, 0), self.DeleteModel)
# self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.AfterMarketOpen("SPY", 300), self.DeleteModel)
self.Train(self.DateRules.Every(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday), self.TimeRules.AfterMarketOpen("SPY", 60), self.Predict)
# moved this function to scratchpad.p.py as going to develop RL trade logic
# self.Train(self.DateRules.Every(DayOfWeek.Friday), self.TimeRules.AfterMarketOpen("SPY", 180), self.TradeDeltaField)
self.Train(self.DateRules.Every(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday), self.TimeRules.BeforeMarketClose("SPY", 60), self.TradeRL)
# wait until RL pieces from stable baselines are fixed to restart this.
self.Schedule.On(self.DateRules.Every(DayOfWeek.Friday), self.TimeRules.At(13, 0), self.WriteResearchData)
# self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.AfterMarketOpen("SPY", 300), self.DeleteModel)
self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY", 300), self.CacheRL)
# self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY", 300), self.CacheLSTM)
self.delta_threshold_positive = []
self.delta_threshold_positive_append = []
self.delta_threshold_negative = []
self.delta_threshold_negative_append = []
self.predict = []
self.epsilon = 0.000000001
# self.AutomaticIndicatorWarmUp = True
self.SetWarmUp(TimeSpan.FromDays(21))
# self.SetWarmUp(TimeSpan.FromDays(100))
# self.SetWarmUp(20, Resolution.Hour)
# self.SetWarmUp(60, Resolution.Hour)
def OnSecuritiesChanged(self, changes):
# This is the period of bars we'll be creating
BarPeriod = TimeSpan.FromMinutes(60)
##Doesn't work use above instead even if switching to hours# BarPeriod = TimeSpan.FromHours(60)
# This is the period of our sma indicators
# SimpleMovingAveragePeriod = 20
SimpleMovingAveragePeriod = 3
# This is the number of consolidated bars we'll hold in symbol data for reference
RollingWindowSize = 60
for security in changes.AddedSecurities:
symbol = security.Symbol
self.DataPNorm[symbol] = SymbolDataPNorm(self, symbol, BarPeriod, SimpleMovingAveragePeriod, RollingWindowSize, self.UniverseSettings.Resolution)
for security in changes.RemovedSecurities:
symbol = security.Symbol
if symbol in self.DataPNorm:
self.SubscriptionManager.RemoveConsolidator(symbol, self.DataPNorm[symbol].consolidator)
symbolData = self.DataPNorm.pop(symbol, None)
if security.Invested:
self.Liquidate(symbol, "Universe Removed Security")
def OnDataConsolidated(self, sender, bar):
if bar.Symbol in self.DataPNorm:
self.DataPNorm[bar.Symbol].STD.Update(bar.EndTime, bar.Close)
if self.DataPNorm[bar.Symbol].STD.IsReady:
std = self.DataPNorm[bar.Symbol].STD.Current.Value
self.DataPNorm[bar.Symbol].HistoricalSTD.Add(std)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].SMA.Update(bar.EndTime, bar.Close)
if self.DataPNorm[bar.Symbol].SMA.IsReady:
sma = self.DataPNorm[bar.Symbol].SMA.Current.Value
self.DataPNorm[bar.Symbol].HistoricalSMA.Add(sma)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].SMA_.Update(bar.EndTime, bar.Close)
if self.DataPNorm[bar.Symbol].SMA_.IsReady:
SMA_ = self.DataPNorm[bar.Symbol].SMA_.Current.Value
self.DataPNorm[bar.Symbol].HistoricalSMA_.Add(SMA_)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].PNorm.Update(bar.EndTime, bar.Close)
if self.DataPNorm[bar.Symbol].PNorm.IsReady:
pnorm = self.DataPNorm[bar.Symbol].PNorm.Current.Value
self.DataPNorm[bar.Symbol].HistoricalPNorm.Add(pnorm)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].VOLSMA.Update(bar.EndTime, bar.Volume)
if self.DataPNorm[bar.Symbol].VOLSMA.IsReady:
VOLSMA = self.DataPNorm[bar.Symbol].VOLSMA.Current.Value
self.DataPNorm[bar.Symbol].HistoricalVOLSMA.Add(VOLSMA)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].VOLMIN.Update(bar.EndTime, bar.Volume)
if self.DataPNorm[bar.Symbol].VOLMIN.IsReady:
VOLMIN = self.DataPNorm[bar.Symbol].VOLMIN.Current.Value
self.DataPNorm[bar.Symbol].HistoricalVOLMIN.Add(VOLMIN)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].VOLMAX.Update(bar.EndTime, bar.Volume)
if self.DataPNorm[bar.Symbol].VOLMAX.IsReady:
VOLMAX = self.DataPNorm[bar.Symbol].VOLMAX.Current.Value
self.DataPNorm[bar.Symbol].HistoricalVOLMAX.Add(VOLMAX)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].volnorm.Update(bar.EndTime, bar.Volume)
if self.DataPNorm[bar.Symbol].volnorm.IsReady:
volnorm = self.DataPNorm[bar.Symbol].volnorm.Current.Value
self.DataPNorm[bar.Symbol].HistoricalVOLNORM.Add(volnorm)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].VOLSTD.Update(bar.EndTime, bar.Volume)
if self.DataPNorm[bar.Symbol].VOLSTD.IsReady:
volstd = self.DataPNorm[bar.Symbol].VOLSTD.Current.Value
self.DataPNorm[bar.Symbol].HistoricalVOLSTD.Add(volstd)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
self.DataPNorm[bar.Symbol].VOLSMA_.Update(bar.EndTime, bar.Volume)
if self.DataPNorm[bar.Symbol].VOLSMA_.IsReady:
VOLSMA_ = self.DataPNorm[bar.Symbol].VOLSMA_.Current.Value
self.DataPNorm[bar.Symbol].HistoricalVOLSMA_.Add(VOLSMA_)
self.DataPNorm[bar.Symbol].Bars.Add(bar)
def OnDataPrep(self,indicator):
# SMA train and test....
_tr = []
_tst = []
new_value=[]
for i in reversed(range(0,self.generator_length)):
value = np.float32(indicator[i])
if len(new_value) == 0:
new_value = value
new_value = np.append(new_value, value)
_tst = new_value.reshape((len(new_value)),1)
_tst = _tst.reshape((len(_tst)),1)
new_value=[]
for i in reversed(range(1,self.train_range)):
value = np.float32(indicator[i])
if len(new_value) == 0:
new_value = value
new_value = np.append(new_value, value)
_tr = new_value.reshape((len(new_value)),1)
return(_tst,_tr)
def OnData(self,data):
if self.IsWarmingUp:
self.Debug('returning because still warming up')
return
self.test = []
self.train = []
self.actual = []
self.actual_sma = []
self.actual_std = []
self.actual_pnorm = []
self.train_c = []
self.train_std = []
self.train_sma = []
self.train_pnorm = []
self.actual_symbols = []
self.actual_volsma = []
self.train_volsma = []
self.train_volstd = []
self.actual_volstd = []
self.actual_volmin = []
self.train_volmin = []
self.actual_volmax = []
self.train_volmax = []
self.actual_volnorm = []
self.train_volnorm = []
train = []
test = []
actual = []
# maybe these need to become dicts, if i'm going to not mix up symbols
for symbol, symbolData in self.DataPNorm.items():
# this check proves that this symbol was JUST updated prior to this OnData function being called
if symbolData.IsReady() and symbolData.WasJustUpdated(self.Time):
c_tst = []
new_value=[]
for i in reversed(range(0,self.generator_length)):
value = np.float32(symbolData.Bars[0].Close)
if len(new_value) == 0:
new_value = value
new_value = np.append(new_value, value)
c_tst = new_value.reshape((len(new_value)),1)
c_tst = c_tst.reshape((len(c_tst)),1)
# i'm not actually iterating on close here or above, this needs to be fixed
# only passing [0] as some issue where not rolling these yet so should come up with better strategy..
c_tr = []
new_value=[]
for i in reversed(range(1,self.train_range)):
value = np.float32(symbolData.Bars[0].Close)
if len(new_value) == 0:
new_value = value
new_value = np.append(new_value, value)
c_tr = new_value.reshape((len(new_value)),1)
c_tr = c_tr.reshape((len(c_tr)),1)
sma_tst, sma_tr = self.OnDataPrep(symbolData.HistoricalSMA)
std_tst, std_tr = self.OnDataPrep(symbolData.HistoricalSTD)
ma_tst, ma_tr = self.OnDataPrep(symbolData.HistoricalPNorm)
volsma_tst, volsma_tr = self.OnDataPrep(symbolData.HistoricalVOLSMA)
volstd_tst, volstd_tr = self.OnDataPrep(symbolData.HistoricalVOLSTD)
volmin_tst, volmin_tr = self.OnDataPrep(symbolData.HistoricalVOLMIN)
volmax_tst, volmax_tr = self.OnDataPrep(symbolData.HistoricalVOLMAX)
volnorm_tst, volnorm_tr = self.OnDataPrep(symbolData.HistoricalVOLNORM)
# this will cause dupes as self. never gets overwritten
# but for some reason self is not storing in append otherwise...
# train.hstack(c_tr)
# train.append(c_tr)
# self.train.append(c_tr)
# self.train.append(sma_tr)
# self.train.append(std_tr)
# self.train.append(ma_tr)
self.train.append(np.multiply(ma_tr,volnorm_tr))
# self.test.append(c_tst)
# self.test.append(sma_tst)
# self.test.append(std_tst)
# self.test.append(ma_tst)
self.test.append(np.multiply(ma_tst, volnorm_tst))
# actual price data
self.actual.append(c_tst)
self.actual_std.append(std_tst)
self.actual_sma.append(sma_tst)
self.actual_pnorm.append(ma_tst)
self.actual_volsma.append(volsma_tst)
self.actual_volstd.append(volstd_tst)
self.actual_volmin.append(volmin_tst)
self.actual_volmax.append(volmax_tst)
self.actual_volnorm.append(volnorm_tst)
self.train_c.append(c_tr)
self.train_std.append(std_tr)
self.train_sma.append(sma_tr)
self.train_pnorm.append(ma_tr)
self.train_volsma.append(volsma_tr)
self.train_volstd.append(volstd_tr)
self.train_volmin.append(volmin_tr)
self.train_volmax.append(volmax_tr)
self.train_volnorm.append(volnorm_tr)
self.actual_symbols.append(str(symbol))
if(len(self.train)>0):
self.dataset_train = hstack(self.train)
self.dataset_test = hstack(self.test)
self.dataset_actual = hstack(self.actual)
self.dataset_actual_std = hstack(self.actual_std)
self.dataset_actual_sma = hstack(self.actual_sma)
self.dataset_actual_pnorm = hstack(self.actual_pnorm)
self.dataset_actual_volsma = hstack(self.actual_volsma)
self.dataset_actual_volstd = hstack(self.actual_volstd)
self.dataset_actual_volmin = hstack(self.actual_volmin)
self.dataset_actual_volmax = hstack(self.actual_volmax)
self.dataset_actual_volnorm = hstack(self.actual_volnorm)
self.dataset_train_c = hstack(self.train_c)
self.dataset_train_std = hstack(self.train_std)
self.dataset_train_sma = hstack(self.train_sma)
self.dataset_train_pnorm = hstack(self.train_pnorm)
self.dataset_train_volsma = hstack(self.train_volsma)
self.dataset_train_volstd = hstack(self.train_volstd)
self.dataset_train_volmin = hstack(self.train_volmin)
self.dataset_train_volmax = hstack(self.train_volmax)
self.dataset_train_volnorm = hstack(self.train_volnorm)
if self.model != None:
if self.delta_threshold_positive != []:
if self.delta_threshold_negative != []:
### WORKOUTLOGIC - need to change how this algebra to work with new volume interaction
# self.predict[0]
# self.unpnorm = ((self.predict[0] * self.dataset_actual_std[0]) + self.dataset_actual_sma[0])
self.pnorm = self.predict/(self.dataset_actual_volnorm + self.epsilon)
self.volnorm = self.predict/self.dataset_actual_pnorm
self.unpnorm = ((self.pnorm[0] * self.dataset_actual_std[0]) + self.dataset_actual_sma[0])
self.unvolnorm = ((self.volnorm[0] * self.dataset_actual_volstd[0]) + self.dataset_actual_volsma[0])
# depends on if you did minmax norm or z-score norm
# self.unvolnorm = (self.volnorm[0] * (self.dataset_actual_volmax[0] - self.dataset_actual_volmin[0])) + self.dataset_actual_volmin[0]
# update these more frequently, both now in the predict, and once a day.
temp_delta = ( self.dataset_actual[0]/self.unpnorm) - 1
# temp_delta = ( self.unpnorm / self.dataset_actual[0] ) - 1
# temp_delta = self.unpnorm - self.dataset_actual[0]
delta = np.where(np.isinf(temp_delta),0,temp_delta)
self.delta_threshold_positive = (self.delta_threshold_positive.clip(min=0) + delta.clip(min=0)) / 2
self.delta_threshold_negative = (self.delta_threshold_negative.clip(max=0) + delta.clip(max=0)) / 2
def quickSave(self, save_name, save_data):
self.ObjectStore.Save(save_name, json.dumps(save_data, cls=EncodeNumpyArray))
def quickRead(self, read_name):
if self.ObjectStore.ContainsKey(read_name):
some_dataset = np.float32(json.loads(self.ObjectStore.Read(read_name)))
else:
some_dataset = []
return some_dataset
def WriteResearchData(self):
# write out the self.dataset_train
if self.model == None:
return
self.quickSave('dataset_train', self.dataset_train)
self.quickSave('dataset_test', self.dataset_test)
self.quickSave('dataset_actual', self.dataset_actual)
self.quickSave('dataset_actual_std', self.dataset_actual_std)
self.quickSave('dataset_actual_sma', self.dataset_actual_sma)
self.quickSave('dataset_actual_pnorm', self.dataset_actual_pnorm)
self.quickSave('dataset_actual_volsma', self.dataset_actual_volsma)
self.quickSave('dataset_actual_volstd', self.dataset_actual_volstd)
self.quickSave('dataset_actual_volmin', self.dataset_actual_volmin)
self.quickSave('dataset_actual_volmax', self.dataset_actual_volmax)
self.quickSave('dataset_actual_volnorm', self.dataset_actual_volnorm)
self.quickSave('delta_threshold_negative', self.delta_threshold_negative)
self.quickSave('delta_threshold_positive', self.delta_threshold_positive)
self.quickSave('dataset_train_c', self.dataset_train_c)
self.quickSave('dataset_train_std', self.dataset_train_std)
self.quickSave('dataset_train_sma', self.dataset_train_sma)
self.quickSave('dataset_train_pnorm', self.dataset_train_pnorm)
self.quickSave('dataset_train_volsma', self.dataset_train_volsma)
self.quickSave('dataset_train_volstd', self.dataset_train_volstd)
self.quickSave('dataset_train_volmin', self.dataset_train_volmin)
self.quickSave('dataset_train_volmax', self.dataset_train_volmax)
self.quickSave('dataset_train_volnorm', self.dataset_train_volnorm)
self.quickSave('dataset_predict', self.predict)
self.quickSave('dataset_predict_pnorm', self.y_predict_pnorm)
self.quickSave('dataset_predict_volnorm', self.y_predict_volnorm)
self.quickSave('dataset_symbols', self.actual_symbols)
# self.quickSave('dataset_live', self.dataset_live.values)
# self.quickSave('dataset_simlive', self.dataset_simlive)
# self.quickSave('dataset_simlive', self.dataset_live_temp.values)
self.Debug('write out some datasets for RL research')
def CacheRL(self):
if self.model_rl == None:
return
self.model_rl_params = self.model_rl.get_parameters()
self.model_rl_params_list = self.model_rl.get_parameter_list()
# model/shared_fc0/w:0
self.Debug('write out weights to persist between backtests')
self.ObjectStore.Save('0a2c_persist', json.dumps(self.model_rl_params, cls=EncodeNumpyArray))
def TradeRL(self):
### long way to go on the below
# data is not very clean, but made progress on creating a simulated env based on predicted montecarlo values
# still need live dataset to be fixed.
self.Debug('got into traderl')
if self.model == None:
self.Debug('returning because model is none')
return
if self.delta_threshold_positive == []:
self.Debug('returning because threshold positive is none')
return
if self.delta_threshold_negative == []:
self.Debug('returning because threshold negative is none')
return
# self.Debug('starting generation of sim data')
dataset_live_agg_arr = []
dataset_simlive = []
for i in range(0,1):
some_data = generateSimData(self.dataset_train, self.y_predict, self.y_predict_pnorm, self.y_predict_volnorm, self.sim_strat)
dataset_live_agg_arr.append(some_data.Data)
dataset_simlive.append(some_data.TradeData)
# self.Debug('finished generation of sim data')
# self.Debug('starting return of some data')
# self.dataset_live_temp = some_data.Data
# self.Debug('starting return of trade data')
# dataset_simlive = some_data.TradeData
# dataset_live_agg_arr = generateSimData(self.y_predict, self.y_predict_pnorm, self.y_predict_volnorm, self.sim_strat)
# can further this do to some kind of limited experience replay, but for now doing full experience replay of all sets seen
# self.dataset_live_temp = pd.concat(dataset_live_agg_arr)
self.dataset_live_append.append(pd.concat(dataset_live_agg_arr))
self.dataset_simlive_append.append(pd.concat(dataset_simlive))
self.dataset_live = pd.concat(self.dataset_live_append)
self.dataset_simlive = pd.concat(self.dataset_simlive_append)
# self.dataset_live['Volume'] = 0
self.Debug(str(self.dataset_live.shape)) # shape of training env dataset
self.Debug(len(self.dataset_live)) # shape of training env dataset
window_size = 2
start_index = window_size
end_index = len(self.dataset_live)
env = StocksEnv(self.dataset_live, window_size, (start_index, end_index))
# env = StocksEnv(self.dataset_live, window_size, (start_index, end_index), self.actual_symbols, self.Transactions, self.Portfolio, self.LimitOrder, self.MarketOrder)
policy_kwargs = dict(net_arch=[64, 'lstm', dict(vf=[128, 128, 128], pi=[64, 64])])
if(self.model_rl==None):
if self.ObjectStore.ContainsKey("0a2c_persist"):
self.Debug('found model between backtests')
ac_weights = json.loads(self.ObjectStore.Read('0a2c_persist'))
self.model_rl = A2C('MlpLstmPolicy', env, verbose=0, policy_kwargs=policy_kwargs)
self.model_rl.load_parameters(ac_weights)
#self.model_rl= A2C.load("ac")
self.model_rl.set_env(DummyVecEnv([lambda: StocksEnv(self.dataset_live,window_size,frame_bound = (start_index, end_index))]))
self.model_rl.learn(total_timesteps=10000)
self.model_rl.save("ac") # why save even if not reloading....
else:
self.Debug('tried overwriting model')
# figure out how to pickle the model and save it once
# then can read that in between backtests
#https://readthedocs.org/projects/stable-baselines/downloads/pdf/master/
#https://readthedocs.org/projects/stable-baselines/downloads/pdf/master/#section*.119
# do get paramaters, then load_parameters
# will have to be a dictionary
self.model_rl = A2C('MlpLstmPolicy', env, verbose=0, policy_kwargs=policy_kwargs)
self.model_rl.learn(total_timesteps=10000)
self.model_rl.save("ac") # why save even if not reloading....
# self.model_rl_params = self.model_rl.get_parameters()
# encoded_json_params = json.dumps(self.model_rl_params, cls=EncodeNumpyArray)
# self.ObjectStore.Save('0a2c_persist', encoded_json_params)
else:
self.Debug('found model between weeks')
self.model_rl= A2C.load("ac")
self.model_rl.set_env(DummyVecEnv([lambda: StocksEnv(self.dataset_live,window_size,frame_bound = (start_index, end_index))]))
self.model_rl.learn(total_timesteps=10000)
self.model_rl.save("ac") # why save even if not reloading....
insights = []
for iter_start, iter_symbol in enumerate(self.easy_symbols):
# tradeXLI
start_here = iter_start
stop_here = iter_start+1
if(stop_here>=len(self.easy_symbols)):
return
# self.dataset_simlive = pd.concat([dataset_simlive.iloc[stop_here:stop_here],dataset_simlive.iloc[stop_here:stop_here],dataset_simlive.iloc[stop_here:stop_here],dataset_simlive.iloc[stop_here:stop_here],dataset_simlive.iloc[stop_here:stop_here],dataset_simlive.iloc[stop_here:stop_here],dataset_simlive.iloc[stop_here:stop_here],dataset_simlive.iloc[stop_here:stop_here]])
# else:
#self.dataset_simlive = pd.concat([dataset_simlive.iloc[start_here:stop_here],dataset_simlive.iloc[start_here:stop_here],dataset_simlive.iloc[start_here:stop_here],dataset_simlive.iloc[start_here:stop_here],dataset_simlive.iloc[start_here:stop_here],dataset_simlive.iloc[start_here:stop_here],dataset_simlive.iloc[start_here:stop_here],dataset_simlive.iloc[start_here:stop_here]])
#self.Debug(str(self.dataset_test.shape))
env_maker = lambda: StocksEnv(self.dataset_simlive, window_size, (start_index, end_index))
env = env_maker()
observation = env.reset()
# do logic with cash available get 1% etc...
# trade xli
while True:
observation = observation[np.newaxis, ...]
# action = env.action_space.sample()
action, _states = self.model_rl.predict(observation)
observation, reward, done, info = env.step(action)
env.render()
if done:
# this needs to be moved to use more real time data...
# plot this.
#self.Debug(str(info))
self.total_reward = info['total_reward']
self.total_profit = info['total_profit']
self.position = info['position']
# if training = False
# then lets actually buy..
if(action[0]==1):
# then buy
symbol = self.easy_symbols[start_here]
self.MarketOrder(symbol, 25)
# insights += [Insight.Price(symbol, timedelta(1), InsightDirection.Up)]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Up) for tempsymbol in self.sp500Sectors.Long if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Up) for tempsymbol in self.energy.Long if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Up) for tempsymbol in self.metals.Long if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Up) for tempsymbol in self.technology.Long if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Up) for tempsymbol in self.treasuries.Long if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Up) for tempsymbol in self.volatility.Long if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights.append(Insight.Price(self.symbols[iter_start], timedelta(1), InsightDirection.Up))
#self.EmitInsights(Insight.Price(symbol, timedelta(1), InsightDirection.Up))
elif(action[0]==0):
# symbol = 'GLD'
symbol = self.easy_symbols[start_here]
self.MarketOrder(symbol, -25)
# insights += [Insight.Price(symbol, timedelta(1), InsightDirection.Down)]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Down) for tempsymbol in self.sp500Sectors.Inverse if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Down) for tempsymbol in self.energy.Inverse if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Down) for tempsymbol in self.metals.Inverse if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Down) for tempsymbol in self.technology.Inverse if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Down) for tempsymbol in self.treasuries.Inverse if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights += [Insight.Price(tempsymbol, timedelta(1), InsightDirection.Down) for tempsymbol in self.volatility.Inverse if ((self.Securities.ContainsKey(tempsymbol)) & (self.Securities[tempsymbol].IsDelisted == False))]
# insights.append(Insight.Price(self.symbols[iter_start], timedelta(1), InsightDirection.Down))
#self.EmitInsights(Insight.Price(symbol, timedelta(1), InsightDirection.Down))
# then sell
# self.Plot('RL Plot', 'total_reward', self.total_reward)
# self.Plot('RL Plot', 'total_profit', self.total_profit)
# self.Plot('RL Plot', 'position Actual', self.position)
break
#self.EmitInsights(insights)
def Predict(self):
if self.model == None:
return
# since introducing this, then i will need to
# use dictionary of symbols and have clipping class strategy for this before mering deltas as size will change if symbol goes missing.
features_set=self.dataset_train[0:]
if(str(features_set.shape[1])!=self.key):
if(self.ObjectStore.ContainsKey(str(features_set.shape[1]))):
self.LoadCachedModel()
else:
return
self.predict = self.model.predict(np.array([self.dataset_test[0:self.generator_length]]))
self.y_predict = np.stack([self.model.predict(np.array([self.dataset_test[0:self.generator_length]])) for x in range(10000)])
self.y_predict_pnorm = self.y_predict/(self.dataset_actual_volnorm[0] + self.epsilon)
self.y_predict_volnorm = self.y_predict/self.dataset_actual_pnorm[0]
self.ObjectStore.Save('dataset_yproba', json.dumps(hstack(self.y_predict), cls=EncodeNumpyArray))
self.predict_proba = self.y_predict.mean(axis=0)
self.predict_std = self.y_predict.std(axis=0)
# self.predict = y_proba # comment this out later during refactoring
# predict = self.model.predict(self.dataset_train, verbose=0)
self.mae = np.mean(np.abs(self.predict[0] - self.dataset_test[0:self.generator_length] ))
self.avg_test = np.mean(self.dataset_test[0:self.generator_length])
#self.avg_predict = np.mean(predict)
# (pnorm * std) + sma
# unpnorm = (predict * std_tst) + sma_tst
self.pnorm = self.predict/(self.dataset_actual_volnorm + self.epsilon)
self.volnorm = self.predict/self.dataset_actual_pnorm
self.unpnorm = ((self.pnorm[0] * self.dataset_actual_std[0]) + self.dataset_actual_sma[0])
self.unvolnorm = ((self.volnorm[0] * self.dataset_actual_volstd[0]) + self.dataset_actual_volsma[0])
# self.unvolnorm = (self.volnorm[0] * (self.dataset_actual_volmax[0] - self.dataset_actual_volmin[0])) + self.dataset_actual_volmin[0]
# self.unpnorm = ((predict[0] * self.dataset_actual_std[0]) + self.dataset_actual_sma[0])
# self.unpnorm = ((predict * self.dataset_actual_std[0]) + self.dataset_actual_sma[0])
self.avg_predict = np.mean(np.abs(self.unpnorm))
self.diff_predict2 = self.dataset_actual[0] - self.unpnorm
self.avg_actual=np.mean(np.abs(self.dataset_actual[0]))
self.mae2 = np.mean(np.abs(self.diff_predict2))
# pop off a few and check their perforamnce
self.ex1_actual = self.unpnorm[0]
self.ex1_predicted = self.dataset_actual[0][0]
self.ex1_actualVOL = self.unvolnorm[0]
self.ex1_predictedVOL = self.dataset_actual_volsma[0][0]
# self.SPY_actual = self.unpnorm[80]
# self.SPY_predicted = self.dataset_actual[0][80]
self.Plot('Trade Plot', 'Example1 Predicted VOL', self.ex1_predictedVOL)
self.Plot('Trade Plot', 'Example1 Actual VOL', self.ex1_actualVOL)
self.Plot('Trade Plot', 'Avg Predict Price', self.avg_predict)
self.Plot('Trade Plot', 'Avg Actual Price', self.avg_actual)
self.Plot('Trade Plot', 'MAE Error', self.mae*100)
self.Plot('Trade Plot', 'Example1 Predicted Price', self.ex1_predicted)
self.Plot('Trade Plot', 'Example1 Actual Price', self.ex1_actual)
# self.Plot('Trade Plot', 'SPY Predicted', self.SPY_predicted)
# self.Plot('Trade Plot', 'SPY Actual', self.SPY_actual)
# working from this one...
# still need to grab the thresholds
### FIX LOGIC UNPNORM interaction VOLUME and price
temp_delta = ( self.dataset_actual[0]/self.unpnorm) - 1
# temp_delta = ( self.unpnorm / self.dataset_actual[0] ) - 1
# temp_delta = self.unpnorm - self.dataset_actual[0]
# temp_delta = ( self.dataset_actual[0] / self.unpnorm ) - 1
# temp_delta = ( pred / act ) - 1
delta = np.where(np.isinf(temp_delta),0,temp_delta)
if(self.delta_threshold_positive == []):
self.delta_threshold_positive = np.zeros(len(self.unpnorm))
self.delta_threshold_positive = (self.delta_threshold_positive.clip(min=0) + delta.clip(min=0)) / 2
# self.Plot()
if(self.delta_threshold_negative == []):
self.delta_threshold_negative = np.zeros(len(self.unpnorm))
self.delta_threshold_negative = (self.delta_threshold_negative.clip(max=0) + delta.clip(max=0)) / 2
# self.Plot()
self.Plot('Trade Plot', 'Delta Positive', np.mean(np.abs(self.delta_threshold_positive)) * 100)
self.Plot('Trade Plot', 'Delta Negative', np.mean(np.abs(self.delta_threshold_negative)) * 100)
self.Plot('Trade Plot', 'AGQ D--', self.delta_threshold_negative[0] * 100)
self.Plot('Trade Plot', 'AGQ D++', self.delta_threshold_positive[0] * 100)
self.Plot('Trade Plot', 'Model Shape', self.key)
def DeleteModel(self):
self.model = None
self.Debug('deleting model')
keys = [str(j).split(',')[0][1:] for _, j in enumerate(self.ObjectStore.GetEnumerator())]
for key in keys:
# print(key)
if(('dataset_' in key) or ('delta_' in key)):
'''dont delete datasets'''
elif('0a2c_persist' in key):
self.Debug('persisting rl model between backtests, create delete rule later based on memory size or some way to randomly prune')
ac_weights = json.loads(self.ObjectStore.Read('0a2c_persist'))
sum_this = []
for item_name, x in ac_weights.items():
x = np.float32(x)
temp_x = x.size * x.itemsize * 1e-6
sum_this.append(temp_x)
self.ac_weights_mb = sum(sum_this)
self.Debug('currently rl weights in mb is: ' + str(self.ac_weights_mb))
self.Log('currently rl weights in mb is: ' + str(self.ac_weights_mb))
else:
self.ObjectStore.Delete(key)
self.Debug('not persisting any models including RL for some time')
# if(key == '0a2c_persist'):
# self.Debug('persisting RL model')
def CreateModel(self):
# maybe dims are wrong on this, but basically need to be able to start stop here so can quickly ramp up boot up time and relaunch maybe every friday or every 2 weeks etc..
if(self.dataset_train==[]):
self.Debug('using cached dataset_train because it is empty still')
self.dataset_train = self.quickRead('dataset_train')
self.dataset_test = self.quickRead('dataset_test')
self.dataset_actual = self.quickRead('dataset_actual')
self.dataset_actual_std = self.quickRead('dataset_actual_std')
self.dataset_actual_sma = self.quickRead('dataset_actual_sma')
self.dataset_actual_pnorm = self.quickRead('dataset_actual_pnorm')
self.dataset_actual_volsma = self.quickRead('dataset_actual_volsma')
self.dataset_actual_volstd = self.quickRead('dataset_actual_volstd')
self.dataset_actual_volmin = self.quickRead('dataset_actual_volmin')
self.dataset_actual_volmax = self.quickRead('dataset_actual_volmax')
self.dataset_actual_volnorm = self.quickRead('dataset_actual_volnorm')
self.delta_threshold_negative = self.quickRead('delta_threshold_negative')
self.delta_threshold_positive = self.quickRead('delta_threshold_positive')
self.dataset_train_c = self.quickRead('dataset_train_c')
self.dataset_train_std = self.quickRead('dataset_train_std')
self.dataset_train_sma = self.quickRead('dataset_train_sma')
self.dataset_train_pnorm = self.quickRead('dataset_train_pnorm')
self.dataset_train_volsma = self.quickRead('dataset_train_volsma')
self.dataset_train_volstd = self.quickRead('dataset_train_volstd')
self.dataset_train_volmin = self.quickRead('dataset_train_volmin')
self.dataset_train_volmax = self.quickRead('dataset_train_volmax')
self.dataset_train_volnorm = self.quickRead('dataset_train_volnorm')
self.predict = self.quickRead('dataset_predict')
self.y_predict_pnorm = self.quickRead('dataset_predict_pnorm')
self.y_predict_volnorm = self.quickRead('dataset_predict_volnorm')
self.actual_symbols = self.quickRead('dataset_symbols')
if(self.dataset_train==[]):
self.Debug('cached datasets were empty, must have recently cleared.')
return
# bring this logic in
#https://www.quantconnect.com/project/8925074
if(self.quick_mape == 'none'):
train_it = True
elif((self.quick_mape < self.quick_mape_floor)):
train_it = False
elif((self.quick_mape_status == 'increasing')):
train_it = True
else:
train_it = False
self.modelIsTraining = True
self.generator = TimeseriesGenerator(self.dataset_train[0:], self.dataset_train[0:], length=self.generator_length, batch_size=128)
features_set=self.dataset_train[0:]
self.key = str(features_set.shape[1])
self.Debug(self.key)
num_macros=self.num_macros
key=self.key
store=self.ObjectStore
_activation = 'elu'
# shouldn't have this in here, should allow flexibility, but just quick adding this for quick test to see how well model is generalizing
# if(str(features_set.shape[1])!=self.key):
# return
if(self.model==None):
inp = tf.keras.layers.Input(shape=(3, features_set.shape[1]), name='input')
x = tf.keras.layers.LSTM(256, name='lstm256', return_sequences=True, activation=_activation, kernel_constraint=tf.keras.constraints.max_norm(max_value=2, axis=0))(inp, training=True)
x = tf.keras.layers.Dropout(0.30, name='dropout30')(x, training=True)
x = tf.keras.layers.GaussianDropout(0.20, name='dropout20')(x, training=True)
x = tf.keras.layers.LSTM(128, name='lstm128', dropout=0.3, recurrent_dropout=0.3, return_sequences=True, activation=_activation)(x, training=True)
x = tf.keras.layers.Dropout(0.30, name='dropouttwo30')(x, training=True)
x = tf.keras.layers.GaussianDropout(0.20, name='dropouttwo20')(x, training=True)
x = tf.keras.layers.LSTM(16, name='lstm16', dropout=0.1, return_sequences=False, activation=_activation)(x, training=True)
out = tf.keras.layers.Dense(features_set.shape[1], name='dense1')(x)
model = tf.keras.models.Model(inputs=inp, outputs=out)
self.model_uncompiled = model
optimizer = tf.keras.optimizers.Adam(learning_rate=0.0005, beta_1=0.9,
beta_2=0.999, epsilon=1e-07,
amsgrad=False,name='Adam')
# optimizer = tf.keras.optimizers.SGD(learning_rate=0.1, momentum=0.0, nesterov=False, name='SGD')
huber = tf.keras.losses.Huber()
# '''
if store.ContainsKey(key):
modelStr = store.Read(key)
config = json.loads(modelStr)['config']
model = tf.keras.models.model_from_json(modelStr)
# '''
model.compile(optimizer=optimizer, loss=huber)
# '''
if store.ContainsKey(key):
layer_dict = dict([(layer.name, layer) for layer in model.layers])
for layer in model.layers:
layer_name = layer.name
print(layer.name)
layer.set_weights(layer_dict[layer_name].get_weights())
layer_weights = layer_dict[layer_name].get_weights()
len_layer_weights = (len(layer_dict[layer_name].get_weights()))
for iter_num,iter_val in enumerate(layer_weights):
if(store.ContainsKey(f'{key}_{layer_name}_{iter_num}')):
decodedWeights = json.loads(store.Read(f'{key}_{layer_name}_{iter_num}'))
layer_weights[iter_num] = np.float32(decodedWeights)
layer.set_weights(layer_weights)
# '''
length = 2
batch_size = 1
n_features = 1
some_epochs = 10
# see if this helps timeout issues, else do more here to make sure total functions returns
time_stopping_callback = TimeStopping(seconds=60, verbose=1) # Red
for epoch in range(some_epochs):
model.fit(self.generator, epochs = epoch, callbacks=[time_stopping_callback])
self.model = model
modelStr = json.dumps(serialize_keras_object(model))
store.Save(key, modelStr)
# i don't think this isright here... i don' need to resave these each time do i?
layer_dict = dict([(layer.name, layer) for layer in model.layers])
for layer in model.layers:
layer_name = layer.name
layer.set_weights(layer_dict[layer_name].get_weights())
layer_weights = layer_dict[layer_name].get_weights()
len_layer_weights = len(layer_weights)
for iter_num,iter_val in enumerate(layer_weights):
encoded_json_params = json.dumps(layer_weights[iter_num], cls=EncodeNumpyArray)
store.Save(f'{key}_{layer_name}_{iter_num}', encoded_json_params)
self.model = model
self.Debug(str('not currently persisting LSTM, but can persist this across back tests if needed...'))
del model
else:
# skipping training need to come up with way to inject noise or reduce memory usage on this model...
#April 2019...
#mem: pmem(rss=13220478976, vms=106624483328, shared=281235456, text=126976, lib=0, data=14504652800, dirty=0)
#Dies about here June 2019
#mem: pmem(rss=13866258432, vms=107773743104, shared=272175104, text=126976, lib=0, data=15169880064, dirty=0)
self.Debug(str('found LSTM self.model so no need to cache/load weights'))
some_epochs = 10
time_stopping_callback = TimeStopping(seconds=60, verbose=1) # Red
for epoch in range(some_epochs):
#gc.collect()
# if(self.mem.rss<=6000478976):
self.model.fit(self.generator, epochs = epoch, callbacks=[time_stopping_callback])
# self.mem = self.get_mem_usage()
# self.Debug('mem: {}'.format(self.mem))
self.modelIsTraining = False
#gc.collect()
#tf.keras.backend.clear_session()
return self.model
def LoadCachedModel(self):
# generator = TimeseriesGenerator(self.dataset_train[0:100], self.dataset_train[0:100], length=1, batch_size=128)
self.generator = TimeseriesGenerator(self.dataset_train[0:], self.dataset_train[0:], length=self.generator_length, batch_size=1)
# features_set=self.dataset_train[0:]
# features_set=self.dataset_train
# labels=self.dataset_test
features_set=self.dataset_train[0:]
self.key = str(features_set.shape[1])
num_macros=self.num_macros
key=self.key
store=self.ObjectStore
_activation = 'elu'
inp = tf.keras.layers.Input(shape=(3, features_set.shape[1]), name='input')
# inp = tf.keras.layers.Input(shape=(1, features_set.shape[1]), name='input')
# inp = tf.keras.layers.Input(shape=(1,features_set.shape[2]), name='input')
x = tf.keras.layers.LSTM(256, name='lstm256', return_sequences=True, activation=_activation, kernel_constraint=tf.keras.constraints.max_norm(max_value=2, axis=0))(inp, training=True)
x = tf.keras.layers.Dropout(0.30, name='dropout30')(x, training=True)
x = tf.keras.layers.GaussianDropout(0.20, name='dropout20')(x, training=True)
x = tf.keras.layers.LSTM(128, name='lstm128', dropout=0.3, recurrent_dropout=0.3, return_sequences=True, activation=_activation)(x, training=True)
x = tf.keras.layers.Dropout(0.30, name='dropouttwo30')(x, training=True)
x = tf.keras.layers.GaussianDropout(0.20, name='dropouttwo20')(x, training=True)
x = tf.keras.layers.LSTM(16, name='lstm16', dropout=0.1, return_sequences=False, activation=_activation)(x, training=True)
out = tf.keras.layers.Dense(features_set.shape[1], name='dense1')(x)
model = tf.keras.models.Model(inputs=inp, outputs=out)
optimizer = tf.keras.optimizers.Adam(learning_rate=0.0005, beta_1=0.9,
beta_2=0.999, epsilon=1e-07,
amsgrad=False,name='Adam')
# optimizer = tf.keras.optimizers.SGD(learning_rate=0.1, momentum=0.0, nesterov=False, name='SGD')
huber = tf.keras.losses.Huber()
if store.ContainsKey(key):
modelStr = store.Read(key)
config = json.loads(modelStr)['config']
model = tf.keras.models.model_from_json(modelStr)
model.compile(optimizer=optimizer, loss=huber)
if store.ContainsKey(key):
layer_dict = dict([(layer.name, layer) for layer in model.layers])
for layer in model.layers:
layer_name = layer.name
print(layer.name)
layer.set_weights(layer_dict[layer_name].get_weights())
layer_weights = layer_dict[layer_name].get_weights()
len_layer_weights = (len(layer_dict[layer_name].get_weights()))
for iter_num,iter_val in enumerate(layer_weights):
if(store.ContainsKey(f'{key}_{layer_name}_{iter_num}')):
decodedWeights = json.loads(store.Read(f'{key}_{layer_name}_{iter_num}'))
layer_weights[iter_num] = np.float32(decodedWeights)
layer.set_weights(layer_weights)
self.model = model
return self.model
# https://github.com/AminHP/gym-anytrading/blob/master/gym_anytrading/envs/trading_env.py
import numpy as np
#from .trading_env import TradingEnv, Actions, Positions
#from tradingenv import TradingEnv, Actions, Positions
import gym
from gym import spaces
from gym.utils import seeding
import numpy as np
from enum import Enum
import matplotlib.pyplot as plt
#from stocksenv import StocksEnv
class Actions(Enum):
Sell = 0
Buy = 1
class Positions(Enum):
Short = 0
Long = 1
def opposite(self):
return Positions.Short if self == Positions.Long else Positions.Long
class TradingEnv(gym.Env):
metadata = {'render.modes': ['human']}
def __init__(self, df, window_size):
assert df.ndim == 2
self.seed()
self.df = df
self.window_size = window_size
self.prices, self.signal_features = self._process_data()
self.shape = (window_size, self.signal_features.shape[1])
# spaces
self.action_space = spaces.Discrete(len(Actions))
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=self.shape, dtype=np.float32)
# episode
self._start_tick = self.window_size
self._end_tick = len(self.prices) - 1
self._done = None
self._current_tick = None
self._last_trade_tick = None
self._position = None
self._position_history = None
self._total_reward = None
self._total_profit = None
self._first_rendering = None
self.history = None
def seed(self, seed=None):
self.np_random, seed = seeding.np_random(seed)
return [seed]
def reset(self):
self._done = False
self._current_tick = self._start_tick
self._last_trade_tick = self._current_tick - 1
self._position = Positions.Short
self._position_history = (self.window_size * [None]) + [self._position]
self._total_reward = 0.
self._total_profit = 1. # unit
self._first_rendering = True
self.history = {}
return self._get_observation()
def step(self, action):
self._done = False
self._current_tick += 1
if self._current_tick == self._end_tick:
self._done = True
step_reward = self._calculate_reward(action)
self._total_reward += step_reward
self._update_profit(action)
trade = False
if ((action == Actions.Buy.value and self._position == Positions.Short) or
(action == Actions.Sell.value and self._position == Positions.Long)):
trade = True
if trade:
self._position = self._position.opposite()
self._last_trade_tick = self._current_tick
self._position_history.append(self._position)
observation = self._get_observation()
info = dict(
total_reward = self._total_reward,
total_profit = self._total_profit,
position = self._position.value
)
self._update_history(info)
return observation, step_reward, self._done, info
def _get_observation(self):
return self.signal_features[(self._current_tick-self.window_size):self._current_tick]
def _update_history(self, info):
if not self.history:
self.history = {key: [] for key in info.keys()}
for key, value in info.items():
self.history[key].append(value)
def render(self, mode='human'):
def _plot_position(position, tick):
color = None
if position == Positions.Short:
color = 'red'
elif position == Positions.Long:
color = 'green'
if color:
plt.scatter(tick, self.prices[tick], color=color)
if self._first_rendering:
self._first_rendering = False
plt.cla()
plt.plot(self.prices)
start_position = self._position_history[self._start_tick]
_plot_position(start_position, self._start_tick)
_plot_position(self._position, self._current_tick)
plt.suptitle(
"Total Reward: %.6f" % self._total_reward + ' ~ ' +
"Total Profit: %.6f" % self._total_profit
)
plt.pause(0.01)
def render_all(self, mode='human'):
window_ticks = np.arange(len(self._position_history))
plt.plot(self.prices)
short_ticks = []
long_ticks = []
for i, tick in enumerate(window_ticks):
if self._position_history[i] == Positions.Short:
short_ticks.append(tick)
elif self._position_history[i] == Positions.Long:
long_ticks.append(tick)
plt.plot(short_ticks, self.prices[short_ticks], 'ro')
plt.plot(long_ticks, self.prices[long_ticks], 'go')
plt.suptitle(
"Total Reward: %.6f" % self._total_reward + ' ~ ' +
"Total Profit: %.6f" % self._total_profit
)
def close(self):
plt.close()
def save_rendering(self, filepath):
plt.savefig(filepath)
def pause_rendering(self):
plt.show()
def _process_data(self):
raise NotImplementedError
def _calculate_reward(self, action):
raise NotImplementedError
def _update_profit(self, action):
raise NotImplementedError
def max_possible_profit(self): # trade fees are ignored
raise NotImplementedError
class StocksEnv(TradingEnv):
def __init__(self, df, window_size, frame_bound):
assert len(frame_bound) == 2
self.frame_bound = frame_bound
super().__init__(df, window_size)
self.trade_fee_bid_percent = 0.01 # unit
self.trade_fee_ask_percent = 0.005 # unit
def _process_data(self):
prices = self.df.loc[:, 'Close'].to_numpy()
prices[self.frame_bound[0] - self.window_size] # validate index (TODO: Improve validation)
prices = prices[self.frame_bound[0]-self.window_size:self.frame_bound[1]]
diff = np.insert(np.diff(prices), 0, 0)
signal_features = np.column_stack((prices, diff))
return prices, signal_features
def _calculate_reward(self, action):
step_reward = 0
trade = False
if ((action == Actions.Buy.value and self._position == Positions.Short) or
(action == Actions.Sell.value and self._position == Positions.Long)):
trade = True
if trade:
current_price = self.prices[self._current_tick]
last_trade_price = self.prices[self._last_trade_tick]
price_diff = current_price - last_trade_price
if self._position == Positions.Long:
step_reward += price_diff
return step_reward
def _update_profit(self, action):
trade = False
if ((action == Actions.Buy.value and self._position == Positions.Short) or
(action == Actions.Sell.value and self._position == Positions.Long)):
trade = True
if trade or self._done:
current_price = self.prices[self._current_tick]
last_trade_price = self.prices[self._last_trade_tick]
if self._position == Positions.Long:
shares = (self._total_profit * (1 - self.trade_fee_ask_percent)) / last_trade_price
self._total_profit = (shares * (1 - self.trade_fee_bid_percent)) * current_price
def max_possible_profit(self):
current_tick = self._start_tick
last_trade_tick = current_tick - 1
profit = 1.
while current_tick <= self._end_tick:
position = None
if self.prices[current_tick] < self.prices[current_tick - 1]:
while (current_tick <= self._end_tick and
self.prices[current_tick] < self.prices[current_tick - 1]):
current_tick += 1
position = Positions.Short
else:
while (current_tick <= self._end_tick and
self.prices[current_tick] >= self.prices[current_tick - 1]):
current_tick += 1
position = Positions.Long
if position == Positions.Long:
current_price = self.prices[current_tick - 1]
last_trade_price = self.prices[last_trade_tick]
shares = profit / last_trade_price
profit = shares * current_price
last_trade_tick = current_tick - 1
return profit