Skip to content

Commit 28c4e06

Browse files
committed
FIX : Load data files
1 parent 336b04b commit 28c4e06

8 files changed

+191
-75
lines changed

SkinokBacktraderUI.py

+34-14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#sys.path.append('D:/perso/trading/anaconda3/backtrader2')
2222
import backtrader as bt
2323
from CerebroEnhanced import *
24+
from PyQt6 import QtWidgets
2425

2526
import sys, os
2627
from DataFile import DataFile
@@ -72,6 +73,8 @@ def __init__(self):
7273

7374
self.dataManager = DataManager()
7475

76+
self.datafileName_to_dataFile={}
77+
7578
# Restore previous session for faster tests
7679
self.loadConfig()
7780

@@ -87,15 +90,15 @@ def loadConfig(self):
8790
# Load previous data files
8891
#for timeframe in self.timeFrameIndex.keys():
8992

90-
for timeframe in userConfig.data.keys():
93+
for timeFrame in userConfig.data.keys():
9194

9295
dataFile = DataFile()
9396

94-
dataFile.filePath = userConfig.data[timeframe]['filePath']
95-
dataFile.fileName = userConfig.data[timeframe]['fileName']
96-
dataFile.timeFormat = userConfig.data[timeframe]['timeFormat']
97-
dataFile.separator = userConfig.data[timeframe]['separator']
98-
dataFile.timeFrame = timeframe
97+
dataFile.filePath = userConfig.data[timeFrame]['filePath']
98+
dataFile.fileName = userConfig.data[timeFrame]['fileName']
99+
dataFile.timeFormat = userConfig.data[timeFrame]['timeFormat']
100+
dataFile.separator = userConfig.data[timeFrame]['separator']
101+
dataFile.timeFrame = timeFrame
99102

100103
if not dataFile.timeFormat in self.dataFiles:
101104
dataFile.dataFrame, errorMessage = self.dataManager.loadDataFrame(dataFile)
@@ -104,19 +107,34 @@ def loadConfig(self):
104107

105108
isEmpty = False
106109

110+
self.datafileName_to_dataFile[dataFile.fileName] = dataFile
111+
107112
# REALLY UGLY : it should be a function of user interface
108113
items = self.interface.strategyTesterUI.loadDataFileUI.dataFilesListWidget.findItems(dataFile.fileName, QtCore.Qt.MatchFixedString)
109114

110115
if len(items) == 0:
111-
self.interface.strategyTesterUI.loadDataFileUI.dataFilesListWidget.addItem(os.path.basename(dataFile.filePath))
116+
self.interface.strategyTesterUI.loadDataFileUI.dataFilesListWidget.addItem(dataFile.fileName)
112117

113-
self.dataFiles[dataFile.timeFrame] = dataFile;
118+
self.dataFiles[dataFile.timeFrame] = dataFile
114119

115120
if not isEmpty:
116121
self.importData()
117122

118123
pass
119124

125+
def removeTimeframe(self, timeFrame):
126+
127+
# Delete from controler
128+
del self.dataFiles[timeFrame]
129+
130+
# Delete from chart
131+
self.interface.deleteChartDock(timeFrame)
132+
133+
# Delete from Cerebro ?
134+
self.resetCerebro()
135+
136+
pass
137+
120138
def resetCerebro(self):
121139

122140
# create a "Cerebro" engine instance
@@ -211,19 +229,21 @@ def addStrategy(self, strategyName):
211229

212230
pass
213231

214-
def strategyParametersChanged(self, lineEdit, parameterName, parameterOldValue):
232+
def strategyParametersChanged(self, widget, parameterName, parameterOldValue):
215233

216234
# todo something
217-
if len(lineEdit.text()) > 0:
235+
if len(widget.text()) > 0:
218236

219237
param = self.strategyClass.params._get(self.strategyClass.params,parameterName)
220238

221-
if isinstance(param, int):
222-
self.strategyParameters[parameterName] = int(lineEdit.text())
239+
if isinstance(param, bool):
240+
self.strategyParameters[parameterName] = widget.checkState() == QtCore.Qt.CheckState.Checked
241+
elif isinstance(param, int):
242+
self.strategyParameters[parameterName] = int(widget.text())
223243
elif isinstance(param, float):
224-
self.strategyParameters[parameterName] = float(lineEdit.text())
244+
self.strategyParameters[parameterName] = float(widget.text())
225245
else:
226-
self.strategyParameters[parameterName] = lineEdit.text()
246+
self.strategyParameters[parameterName] = widget.text()
227247

228248
pass
229249

finplotWindow.py

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def __init__(self, dockArea, dockChart, interface):
4343

4444
self.last_ax_data_xtick = []
4545

46-
4746
pass
4847

4948
#########

loadDataFilesUI.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def loadDataFileFromConfig(self, dataPath, datetimeFormat, separator):
114114

115115
return df
116116

117+
117118
def deleteFile(self):
118119

119120
listItems=self.dataFilesListWidget.selectedItems()
@@ -122,13 +123,13 @@ def deleteFile(self):
122123
itemTaken = self.dataFilesListWidget.takeItem(self.dataFilesListWidget.row(item))
123124

124125
# Delete from dataFrames
125-
del self.controller.dataframes[itemTaken.text()]
126-
127-
# Delete from Cerebro ?
126+
timeFrame = self.controller.datafileName_to_dataFile[itemTaken.text()].timeFrame
128127

128+
# Delete from controler
129+
self.controller.removeTimeframe(timeFrame)
129130

130131
# Delete from config
131-
self.userConfig.removeParameter(timeframe);
132+
self.userConfig.removeParameter(timeFrame)
132133

133134
pass
134135

strategies/AiStableBaselinesModel.py

+119-24
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@
2020

2121
import metaStrategy as mt
2222

23-
from stable_baselines3 import PPO
23+
from stable_baselines3 import PPO, DQN
2424

2525
import numpy as np
2626
from enum import Enum
2727

2828
import pandas as pd
29+
import pandas_ta as ta
2930

3031
from custom_indicators import BollingerBandsBandwitch
3132

@@ -40,10 +41,14 @@ class AiStableBaselinesModel(mt.MetaStrategy):
4041

4142
params = (
4243
('model', ""), # Model name
43-
('tradeSize', 5000),
44+
('tradeSize', 10.0),
45+
('use_ATR_SL', True),
4446
('atrperiod', 14), # ATR Period (standard)
45-
('atrdist_SL', 3), # ATR distance for stop price
46-
('atrdist_TP', 5), # ATR distance for take profit price
47+
('atrdist_SL', 3.0), # ATR distance for stop price
48+
('atrdist_TP', 5.0), # ATR distance for take profit price
49+
('use_Fixed_SL', False),
50+
('fixed_SL', 50.0), # Fixed distance Stop Loss
51+
('fixed_TP', 100.0), # Fixed distance Take Profit
4752
)
4853

4954
def notify_order(self, order):
@@ -60,7 +65,7 @@ def __init__(self, *argv):
6065
super().__init__(argv[0])
6166

6267
# Ichi indicator
63-
self.ichimoku = bt.ind.Ichimoku()
68+
self.ichimoku = bt.ind.Ichimoku(self.data)
6469

6570
# To set the stop price
6671
self.atr = bt.indicators.ATR(self.data, period=self.p.atrperiod)
@@ -86,71 +91,78 @@ def __init__(self, *argv):
8691

8792
self.macd = bt.ind.MACDHisto(self.data)
8893

94+
self.normalization_bounds_df_min = self.loadNormalizationBoundsCsv( "C:/perso/AI/AI_Framework/prepared_data/normalization_bounds_min.csv" ).transpose()
95+
self.normalization_bounds_df_max = self.loadNormalizationBoundsCsv( "C:/perso/AI/AI_Framework/prepared_data/normalization_bounds_max.csv" ).transpose()
96+
8997
pass
9098

9199
def start(self):
92100
self.order = None # sentinel to avoid operrations on pending order
93101

94102
# Load the model
95103
self.model = PPO.load(self.p.model)
104+
#self.model = DQN.load(self.p.model)
96105
pass
97106

98107
def next(self):
99108

100109
self.obseravation = self.next_observation()
101110

111+
# Normalize observation
112+
#self.normalizeObservations()
113+
102114
# Do nothing if a parameter is not valid yet (basically wait for all idicators to be loaded)
115+
103116
if pd.isna(self.obseravation).any():
104117
print("Waiting indicators")
105118
return
106-
119+
107120
# Prepare data for Model
108121
action, _states = self.model.predict(self.obseravation) # deterministic=True
109122

110123
if not self.position: # not in the market
111124

125+
# TP & SL calculation
126+
loss_dist = self.atr[0] * self.p.atrdist_SL if self.p.use_ATR_SL else self.p.fixed_SL
127+
profit_dist = self.atr[0] * self.p.atrdist_TP if self.p.use_ATR_SL else self.p.fixed_TP
128+
112129
if action == Action.SELL.value:
113130
self.order = self.sell(size=self.p.tradeSize)
114-
ldist = self.atr[0] * self.p.atrdist_SL
115-
self.lstop = self.data.close[0] + ldist
116-
pdist = self.atr[0] * self.p.atrdist_TP
117-
self.take_profit = self.data.close[0] - pdist
131+
self.lstop = self.data.close[0] + loss_dist
132+
self.take_profit = self.data.close[0] - profit_dist
118133

119134
elif action == Action.BUY.value:
120135
self.order = self.buy(size=self.p.tradeSize)
121-
ldist = self.atr[0] * self.p.atrdist_SL
122-
self.lstop = self.data.close[0] - ldist
123-
pdist = self.atr[0] * self.p.atrdist_TP
124-
self.take_profit = self.data.close[0] + pdist
136+
self.lstop = self.data.close[0] - loss_dist
137+
self.take_profit = self.data.close[0] + profit_dist
125138

126139
else: # in the market
127140
pclose = self.data.close[0]
128-
pstop = self.lstop # seems to be the bug
129141

130-
if (not ((pstop<pclose<self.take_profit)|(pstop>pclose>self.take_profit))):
142+
if (not ((self.lstop<pclose<self.take_profit)|(self.lstop>pclose>self.take_profit))):
131143
self.close() # Close position
132144

133145
pass
134146

135147
# Here you have to transform self object price and indicators into a np.array input for AI Model
136148
# How you do it depend on your AI Model inputs
137149
# Strategy is in the data preparation for AI :D
138-
def next_observation(self):
139-
140-
# https://stackoverflow.com/questions/53979199/tensorflow-keras-returning-multiple-predictions-while-expecting-one
150+
# https://stackoverflow.com/questions/53979199/tensorflow-keras-returning-multiple-predictions-while-expecting-one
141151

142-
# Ichimoku
143-
#inputs = [ self.ichimoku.tenkan[0], self.ichimoku.kijun[0], self.ichimoku.senkou[0], self.ichimoku.senkou_lead[0], self.ichimoku.chikou[0] ]
152+
def next_observation(self):
144153

145154
# OHLCV
146155
inputs = [ self.data.open[0],self.data.high[0],self.data.low[0],self.data.close[0],self.data.volume[0] ]
147156

148-
# Stochastic
149-
inputs = inputs + [self.stochastic.percK[0], self.stochastic.percD[0]]
157+
# Ichimoku
158+
inputs = inputs + [ self.ichimoku.senkou_span_a[0], self.ichimoku.senkou_span_b[0], self.ichimoku.tenkan_sen[0], self.ichimoku.kijun_sen[0], self.ichimoku.chikou_span[0] ]
150159

151160
# Rsi
152161
inputs = inputs + [self.rsi.rsi[0]]
153162

163+
# Stochastic
164+
inputs = inputs + [self.stochastic.percK[0], self.stochastic.percD[0]]
165+
154166
# bbands
155167
inputs = inputs + [self.bbands.bot[0]] # BBL
156168
inputs = inputs + [self.bbands.mid[0]] # BBM
@@ -180,4 +192,87 @@ def next_observation(self):
180192

181193
return np.array(inputs)
182194

183-
pass
195+
pass
196+
197+
198+
def normalizeObservations(self):
199+
200+
MIN_BITCOIN_VALUE = 10_000.0
201+
MAX_BITCOIN_VALUE = 80_000.0
202+
203+
self.obseravation_normalized = np.empty(len(self.obseravation))
204+
try:
205+
# Normalize data
206+
for index, value in enumerate(self.obseravation):
207+
if value < 100.0:
208+
self.obseravation_normalized[index] = (value - 0.0) / (100.0 - 0.0)
209+
else:
210+
self.obseravation_normalized[index] = (value - MIN_BITCOIN_VALUE) / (MAX_BITCOIN_VALUE - MIN_BITCOIN_VALUE)
211+
212+
#self.obseravation_normalized = (self.obseravation-self.normalization_bounds_df_min) / (self.normalization_bounds_df_max-self.normalization_bounds_df_min)
213+
214+
215+
except ValueError as err:
216+
return None, "ValueError error:" + str(err)
217+
except AttributeError as err:
218+
return None, "AttributeError error:" + str(err)
219+
except IndexError as err:
220+
return None, "IndexError error:" + str(err)
221+
except:
222+
aie = 1
223+
224+
pass
225+
226+
def loadNormalizationBoundsCsv(self, filePath):
227+
228+
# Try importing data file
229+
# We should code a widget that ask for options as : separators, date format, and so on...
230+
try:
231+
232+
# Python contains
233+
if pd.__version__<'2.0.0':
234+
df = pd.read_csv(filePath,
235+
sep=";",
236+
parse_dates=None,
237+
date_parser=lambda x: pd.to_datetime(x, format=""),
238+
skiprows=0,
239+
header=None,
240+
index_col=0)
241+
else:
242+
df = pd.read_csv(filePath,
243+
sep=";",
244+
parse_dates=None,
245+
date_format="",
246+
skiprows=0,
247+
header=None,
248+
index_col=0)
249+
250+
return df
251+
252+
except ValueError as err:
253+
return None, "ValueError error:" + str(err)
254+
except AttributeError as err:
255+
return None, "AttributeError error:" + str(err)
256+
except IndexError as err:
257+
return None, "IndexError error:" + str(err)
258+
259+
pass
260+
261+
262+
# https://stackoverflow.com/questions/53321608/extract-dataframe-from-pandas-datafeed-in-backtrader
263+
def __bt_to_pandas__(self, btdata, len):
264+
get = lambda mydata: mydata.get(ago=0, size=len)
265+
266+
fields = {
267+
'open': get(btdata.open),
268+
'high': get(btdata.high),
269+
'low': get(btdata.low),
270+
'close': get(btdata.close),
271+
'volume': get(btdata.volume)
272+
}
273+
time = [btdata.num2date(x) for x in get(btdata.datetime)]
274+
275+
return pd.DataFrame(data=fields, index=time)
276+
277+
pass
278+

userConfig.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def saveParameter(self, parameter, value):
3737
pass
3838

3939
def removeParameter(self, parameter):
40-
if parameter in self.data[parameter]:
40+
if parameter in self.data:
4141
del self.data[parameter]
4242
self.saveConfig()
4343
pass

userData.json

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
{
22
"M5": {
3-
"fileName": "EURUSD_M5.csv",
4-
"filePath": "C:/perso/trading/anaconda3/backtrader-ichimoku/data/Source 1/EURUSD_M5.csv",
3+
"fileName": "BTC_USDT_USDT-5m-futures-train.csv",
4+
"filePath": "C:/perso/AI/AI_Framework/freqtrade_data/binance/futures/BTC_USDT_USDT-5m-futures-train.csv",
55
"separator": ",",
6-
"timeFormat": "%Y-%m-%d %H:%M",
6+
"timeFormat": "%Y-%m-%d %H:%M:%S",
77
"timeFrame": "M5"
8-
},
9-
"M15": {
10-
"fileName": "EURUSD_M15.csv",
11-
"filePath": "C:/perso/trading/anaconda3/backtrader-ichimoku/data/Source 1/EURUSD_M15.csv",
12-
"separator": ",",
13-
"timeFormat": "%Y-%m-%d %H:%M",
14-
"timeFrame": "M15"
158
}
169
}

0 commit comments

Comments
 (0)