Skip to content

Commit 336b04b

Browse files
committed
Add load config to avoid settings data file at each launch
1 parent 2944f22 commit 336b04b

13 files changed

+205
-155
lines changed

DataFile.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
class DataFile():
3+
4+
def __init__(self):
5+
6+
# File location
7+
self.filePath = ""
8+
self.fileName = ""
9+
10+
# File import parameters
11+
self.timeFrame = ""
12+
self.separator = ""
13+
self.timeFormat = ""
14+
15+
# Panda data frame
16+
self.dataFrame = None
17+
18+
pass
19+
20+
21+
22+
23+
24+

SkinokBacktraderUI.py

+36-28
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from CerebroEnhanced import *
2424

2525
import sys, os
26+
from DataFile import DataFile
2627

2728
from dataManager import DataManager
2829
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/observers')
@@ -38,6 +39,8 @@
3839
from wallet import Wallet
3940
from userConfig import UserConfig
4041

42+
from PyQt6 import QtCore
43+
4144
class SkinokBacktraderUI:
4245

4346
def __init__(self):
@@ -48,7 +51,7 @@ def __init__(self):
4851

4952
# Init attributes
5053
self.strategyParameters = {}
51-
self.dataframes = {}
54+
self.dataFiles = {}
5255

5356
# Global is here to update the Ui in observers easily, if you find a better way, don't hesistate to tell me (Skinok)
5457
global interface
@@ -82,23 +85,32 @@ def loadConfig(self):
8285
isEmpty = True
8386

8487
# Load previous data files
85-
for timeframe in self.timeFrameIndex.keys():
88+
#for timeframe in self.timeFrameIndex.keys():
8689

87-
if timeframe in userConfig.data.keys():
88-
89-
filePath = userConfig.data[timeframe]['filePath']
90-
timeFormat = userConfig.data[timeframe]['timeFormat']
91-
separator = userConfig.data[timeframe]['separator']
90+
for timeframe in userConfig.data.keys():
91+
92+
dataFile = DataFile()
93+
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
9299

93-
fileName = os.path.basename(filePath)
100+
if not dataFile.timeFormat in self.dataFiles:
101+
dataFile.dataFrame, errorMessage = self.dataManager.loadDataFrame(dataFile)
94102

95-
df, errorMessage = self.dataManager.loadDataFile(filePath,timeFormat, separator)
103+
if dataFile.dataFrame is not None:
96104

97-
if df is not None:
98-
self.dataframes[fileName] = df
99105
isEmpty = False
100-
else:
101-
print(f" Error loading user data file : {errorMessage}")
106+
107+
# REALLY UGLY : it should be a function of user interface
108+
items = self.interface.strategyTesterUI.loadDataFileUI.dataFilesListWidget.findItems(dataFile.fileName, QtCore.Qt.MatchFixedString)
109+
110+
if len(items) == 0:
111+
self.interface.strategyTesterUI.loadDataFileUI.dataFilesListWidget.addItem(os.path.basename(dataFile.filePath))
112+
113+
self.dataFiles[dataFile.timeFrame] = dataFile;
102114

103115
if not isEmpty:
104116
self.importData()
@@ -140,17 +152,17 @@ def resetCerebro(self):
140152
def importData(self):
141153

142154
try:
143-
144-
fileNames = list(self.dataframes.keys())
155+
156+
timeFrames = list(self.dataFiles.keys())
145157

146158
# Sort data by timeframe
147159
# For cerebro, we need to add lower timeframes first
148-
fileNames.sort( key=lambda x: self.timeFrameIndex[self.dataManager.findTimeFrame(self.dataframes[x])] )
160+
timeFrames.sort( key=lambda x: self.timeFrameIndex[x] )
149161

150162
# Files should be loaded in the good order
151-
for fileName in fileNames:
163+
for timeFrame in timeFrames:
152164

153-
df = self.dataframes[fileName]
165+
df = self.dataFiles[timeFrame].dataFrame
154166

155167
# Datetime first column : 2012-12-28 17:45:00
156168
#self.dataframe['TimeInt'] = pd.to_datetime(self.dataframe.index).astype('int64') # use finplot's internal representation, which is ns
@@ -161,24 +173,21 @@ def importData(self):
161173
# Add data to cerebro : only add data when all files have been selected for multi-timeframes
162174
self.cerebro.adddata(self.data) # Add the data feed
163175

164-
# Find timeframe
165-
timeframe = self.dataManager.findTimeFrame(df)
166-
167176
# Create the chart window for the good timeframe (if it does not already exists?)
168-
self.interface.createChartDock(timeframe)
177+
self.interface.createChartDock(timeFrame)
169178

170179
# Draw charts based on input data
171-
self.interface.drawChart(df, timeframe)
180+
self.interface.drawChart(df, timeFrame)
172181

173182
# Enable run button
174183
self.interface.strategyTesterUI.runBacktestBtn.setEnabled(True)
175184

176185
return True
177186

178187
except AttributeError as e:
179-
print("AttributeError error:" + str(e))
188+
print("AttributeError error:" + str(e) + " " + str(sys.exc_info()[0]))
180189
except KeyError as e:
181-
print("KeyError error:" + str(e))
190+
print("KeyError error:" + str(e) + " " + str(sys.exc_info()[0]))
182191
except:
183192
print("Unexpected error:" + str(sys.exc_info()[0]))
184193
return False
@@ -262,12 +271,11 @@ def displayStrategyResults(self):
262271
#self.interface.createTransactionsUI(self.portfolio_transactions)
263272
self.interface.fillSummaryUI(self.strat_results.stats.broker.cash[0], self.strat_results.stats.broker.value[0], self.strat_results.analyzers.ta.get_analysis())
264273
self.interface.fillTradesUI(self.strat_results._trades.items())
265-
274+
self.interface.dock_strategyResultsUI.show()
266275
#self.interface.drawTrades(self.strat_results._trades.items())
267276
#Orders filters
268277
self.myOrders = []
269278
for order in self.strat_results._orders:
270-
271279
if order.status in [order.Completed]:
272280
self.myOrders.append(order)
273281

@@ -281,7 +289,7 @@ def displayStrategyResults(self):
281289
pnl_data['cash'] = self.wallet.cash_list
282290

283291
# really uggly
284-
pnl_data['time'] = list(self.dataframes.values())[0].index
292+
pnl_data['time'] = list(self.dataFiles.values())[0].dataFrame.index
285293

286294
# draw charts
287295
df = pd.DataFrame(pnl_data)

dataManager.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,27 @@ def DatetimeFormat(self, dataFilePath):
3232

3333
# Return True if loading is successfull & the error string if False
3434
# dataPath is the full file path
35-
def loadDataFile(self, dataPath, datetimeFormat, separator):
35+
def loadDataFrame(self, loadDataFile):
3636

3737
# Try importing data file
3838
# We should code a widget that ask for options as : separators, date format, and so on...
3939
try:
4040

4141
# Python contains
4242
if pd.__version__<'2.0.0':
43-
df = pd.read_csv(dataPath,
44-
sep=separator,
43+
df = pd.read_csv(loadDataFile.filePath,
44+
sep=loadDataFile.separator,
4545
parse_dates=[0],
46-
date_parser=lambda x: pd.to_datetime(x, format=datetimeFormat),
46+
date_parser=lambda x: pd.to_datetime(x, format=loadDataFile.timeFormat),
4747
skiprows=0,
4848
header=0,
4949
names=["Time", "Open", "High", "Low", "Close", "Volume"],
5050
index_col=0)
5151
else:
52-
df = pd.read_csv(dataPath,
53-
sep=separator,
52+
df = pd.read_csv(loadDataFile.filePath,
53+
sep=loadDataFile.separator,
5454
parse_dates=[0],
55-
date_format=datetimeFormat,
55+
date_format=loadDataFile.timeFormat,
5656
skiprows=0,
5757
header=0,
5858
names=["Time", "Open", "High", "Low", "Close", "Volume"],

finplotWindow.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,6 @@ def drawOrders(self, orders = None):
129129
##############
130130
if order.isbuy():
131131

132-
direction = "buy"
133-
134132
# Tracer les traites allant des ouvertures de positions vers la fermeture de position
135133
if currentPositionSize < 0:
136134

@@ -172,8 +170,6 @@ def drawOrders(self, orders = None):
172170
# Sell
173171
##############
174172
elif order.issell():
175-
direction = "sell"
176-
177173

178174
if currentPositionSize < 0:
179175
# Augmentation de la postion
@@ -220,7 +216,7 @@ def drawOrders(self, orders = None):
220216
currentPositionSize += order.size
221217

222218
# Todo: We could display the size of the order with a label on the chart
223-
fplt.add_order(bt.num2date(order.executed.dt), order.executed.price, direction, ax=self.ax0)
219+
fplt.add_order(bt.num2date(order.executed.dt), order.executed.price, order.isbuy(), ax=self.ax0)
224220

225221
pass
226222

loadDataFilesUI.py

+58-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from PyQt6 import QtCore, QtWidgets, uic
22
import pandas as pd
3-
import os
3+
import os
4+
from DataFile import DataFile
45

56
from userConfig import UserConfig
67
from dataManager import DataManager
@@ -33,13 +34,16 @@ def __init__(self, controller, parent = None):
3334
self.deletePB = self.findChild(QtWidgets.QLineEdit, "deletePB")
3435
self.importPB = self.findChild(QtWidgets.QPushButton, "importPB")
3536

37+
self.deleteDataFilePB = self.findChild(QtWidgets.QPushButton, "deleteDataFilePB")
38+
3639
self.errorLabel = self.findChild(QtWidgets.QLabel, "errorLabel")
3740

3841
self.dataFilesListWidget = self.findChild(QtWidgets.QListWidget, "dataFilesListWidget")
3942

4043
# Connect slots : open file
4144
self.openFilePB.clicked.connect( self.openFileDialog )
42-
self.loadFilePB.clicked.connect( self.loadFile )
45+
self.loadFilePB.clicked.connect( self.createDataFile )
46+
self.deleteDataFilePB.clicked.connect(self.deleteFile)
4347
self.importPB.clicked.connect( self.importFiles )
4448

4549
self.dataManager = DataManager()
@@ -55,56 +59,83 @@ def openFileDialog(self):
5559

5660
pass
5761

58-
def loadFile(self):
62+
def createDataFile(self):
5963

60-
# try loading file by controller
61-
separator = '\t' if self.tabRB.isChecked() else ',' if self.commaRB.isChecked() else ';'
64+
dataFile = DataFile()
6265

63-
timeFormat = self.datetimeFormatLE.text()
66+
# try loading file by controller
67+
dataFile.separator = '\t' if self.tabRB.isChecked() else ',' if self.commaRB.isChecked() else ';'
68+
dataFile.timeFormat = self.datetimeFormatLE.text()
69+
dataFile.filePath = self.dataFilePath
70+
dataFile.fileName = os.path.basename(self.dataFilePath)
6471

65-
if not self.dataFilePath in self.controller.dataframes:
72+
if not dataFile.timeFormat in self.controller.dataFiles:
6673

67-
fileName = os.path.basename(self.dataFilePath)
74+
dataFile.dataFrame, errorMessage = self.dataManager.loadDataFrame(dataFile)
6875

69-
df, errorMessage = self.dataManager.loadDataFile(self.dataFilePath, timeFormat, separator)
76+
# Store data file in the user config parameters to later use
77+
dataFile.timeFrame = self.dataManager.findTimeFrame(dataFile.dataFrame)
7078

71-
if df is not None:
72-
73-
# ugly to reference the controller this way
74-
self.controller.dataframes[fileName] = df
79+
if dataFile.dataFrame is not None:
7580

7681
self.errorLabel.setStyleSheet("color:green")
7782
self.errorLabel.setText("The file has been loaded correctly.")
7883

7984
# Add file name
80-
items = self.dataFilesListWidget.findItems(fileName, QtCore.Qt.MatchFixedString)
85+
items = self.dataFilesListWidget.findItems(dataFile.fileName, QtCore.Qt.MatchFixedString)
8186

8287
if len(items) == 0:
83-
self.dataFilesListWidget.addItem(os.path.basename(self.dataFilePath))
84-
85-
# Store data file in the user config parameters to later use
86-
timeframe = self.dataManager.findTimeFrame(df)
87-
self.userConfig.saveParameter(timeframe, {"filePath": self.dataFilePath, "separator" : separator, "timeFormat": timeFormat})
88+
self.dataFilesListWidget.addItem(os.path.basename(dataFile.filePath))
89+
90+
self.controller.dataFiles[dataFile.timeFrame] = dataFile;
91+
self.userConfig.saveObject(dataFile.timeFrame, dataFile)
8892

8993
else:
9094
self.errorLabel.setStyleSheet("color:red")
9195
self.errorLabel.setText(errorMessage)
92-
96+
else:
97+
self.errorLabel.setStyleSheet("color:red")
98+
self.errorLabel.setText("The file is already in the list")
9399
pass
94100

95101

96-
def importFiles(self):
102+
def loadDataFileFromConfig(self, dataPath, datetimeFormat, separator):
103+
104+
fileName = os.path.basename(dataPath)
105+
df, errorMessage = self.dataManager.loadDataFrame(dataPath, datetimeFormat, separator)
106+
107+
if df is not None:
97108

98-
# Get all element in list widget
99-
#items = []
100-
#for x in range(self.dataFilesListWidget.count()):
101-
#items.append(self.dataFilesListWidget.item(x).text())
109+
# Add file name
110+
items = self.dataFilesListWidget.findItems(fileName, QtCore.Qt.MatchFixedString)
102111

103-
# Sort item by timeframe
112+
if len(items) == 0:
113+
self.dataFilesListWidget.addItem(os.path.basename(dataPath))
114+
115+
return df
116+
117+
def deleteFile(self):
118+
119+
listItems=self.dataFilesListWidget.selectedItems()
120+
if not listItems: return
121+
for item in listItems:
122+
itemTaken = self.dataFilesListWidget.takeItem(self.dataFilesListWidget.row(item))
123+
124+
# Delete from dataFrames
125+
del self.controller.dataframes[itemTaken.text()]
126+
127+
# Delete from Cerebro ?
128+
129+
130+
# Delete from config
131+
self.userConfig.removeParameter(timeframe);
132+
133+
pass
134+
135+
def importFiles(self):
104136

105137
# Give all ordered data path to the controller
106138
if self.controller.importData():
107-
self.dataFilesListWidget.clear()
108139
self.hide()
109140

110141
pass

0 commit comments

Comments
 (0)