dijkies 0.0.3__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dijkies/data_pipeline.py +33 -2
- dijkies/deployment.py +173 -60
- dijkies/exceptions.py +23 -0
- dijkies/exchange_market_api.py +5 -9
- dijkies/executors.py +82 -150
- dijkies/performance.py +3 -5
- dijkies/strategy.py +67 -6
- {dijkies-0.0.3.dist-info → dijkies-0.1.1.dist-info}/METADATA +167 -31
- dijkies-0.1.1.dist-info/RECORD +12 -0
- {dijkies-0.0.3.dist-info → dijkies-0.1.1.dist-info}/WHEEL +1 -1
- dijkies/backtest.py +0 -98
- dijkies/credentials.py +0 -6
- dijkies/evaluate.py +0 -235
- dijkies-0.0.3.dist-info/RECORD +0 -15
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dijkies
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: A python framework that can be used to create, test and deploy trading algorithms.
|
|
5
5
|
Author: Arnold Dijk
|
|
6
6
|
Author-email: Arnold Dijk <arnold.dijk@teamrockstars.nl>
|
|
7
7
|
License: MIT
|
|
8
|
-
Requires-Dist: mlflow>=3.7.0
|
|
9
8
|
Requires-Dist: pandas>=2.3.3
|
|
10
|
-
Requires-Dist: pre-commit>=4.5.0
|
|
11
9
|
Requires-Dist: pydantic>=2.12.5
|
|
12
|
-
Requires-Dist: pytest>=9.0.2
|
|
13
10
|
Requires-Dist: python-binance>=1.0.33
|
|
14
11
|
Requires-Dist: python-bitvavo-api>=1.4.3
|
|
15
|
-
Requires-Dist: ta>=0.11.0
|
|
16
|
-
Requires-Dist: tenacity>=9.1.2
|
|
17
12
|
Requires-Python: >=3.11
|
|
13
|
+
Project-URL: Documentation, https://github.com/ArnoldDijk/dijkies/blob/dev/README.md
|
|
14
|
+
Project-URL: Homepage, https://github.com/ArnoldDijk/dijkies
|
|
15
|
+
Project-URL: Issues, https://github.com/ArnoldDijk/dijkies/issues
|
|
16
|
+
Project-URL: Source, https://github.com/ArnoldDijk/dijkies
|
|
18
17
|
Description-Content-Type: text/markdown
|
|
19
18
|
|
|
20
19
|
# Dijkies
|
|
@@ -53,19 +52,19 @@ At a high level, Dijkies operates as follows:
|
|
|
53
52
|
|
|
54
53
|
## Key Design Principles
|
|
55
54
|
|
|
56
|
-
- **Strategy–Executor separation**
|
|
55
|
+
- **Strategy–Executor separation**
|
|
57
56
|
Trading logic is completely decoupled from execution logic.
|
|
58
57
|
|
|
59
|
-
- **Single interface for backtesting and live trading**
|
|
58
|
+
- **Single interface for backtesting and live trading**
|
|
60
59
|
Switching between backtesting and live trading requires no strategy changes.
|
|
61
60
|
|
|
62
|
-
- **Explicit state management**
|
|
61
|
+
- **Explicit state management**
|
|
63
62
|
All balances and positions are tracked in a transparent `State` object.
|
|
64
63
|
|
|
65
|
-
- **Minimal assumptions**
|
|
64
|
+
- **Minimal assumptions**
|
|
66
65
|
Dijkies does not enforce indicators, timeframes, or asset types.
|
|
67
66
|
|
|
68
|
-
- **Composable and extensible**
|
|
67
|
+
- **Composable and extensible**
|
|
69
68
|
New exchanges, execution models, and risk layers can be added easily.
|
|
70
69
|
|
|
71
70
|
## Who Is This For?
|
|
@@ -92,19 +91,25 @@ This quick start shows how to define a strategy, fetch market data, and run a ba
|
|
|
92
91
|
|
|
93
92
|
### 1. Define a Strategy
|
|
94
93
|
|
|
95
|
-
A strategy is a class that inherits from `Strategy` and implements the `execute` method.
|
|
94
|
+
A strategy is a class that inherits from `Strategy` and implements the `execute` method.
|
|
96
95
|
It receives a rolling dataframe of candles and decides when to place orders.
|
|
97
96
|
|
|
98
97
|
```python
|
|
99
|
-
|
|
98
|
+
# create strategy
|
|
99
|
+
|
|
100
100
|
from dijkies.executors import ExchangeAssetClient
|
|
101
|
+
from dijkies.strategy import Strategy
|
|
102
|
+
|
|
101
103
|
from ta.momentum import RSIIndicator
|
|
102
|
-
|
|
104
|
+
from pandas.core.frame import DataFrame as PandasDataFrame
|
|
105
|
+
|
|
106
|
+
from dijkies.executors import BacktestExchangeAssetClient, State
|
|
107
|
+
|
|
108
|
+
from dijkies.data_pipeline import DataPipeline, NoDataPipeline
|
|
103
109
|
|
|
104
110
|
|
|
105
111
|
class RSIStrategy(Strategy):
|
|
106
|
-
|
|
107
|
-
analysis_dataframe_size_in_minutes = 60 * 24 * 30 # 30 days
|
|
112
|
+
analysis_dataframe_size_in_minutes = 60*24*30
|
|
108
113
|
|
|
109
114
|
def __init__(
|
|
110
115
|
self,
|
|
@@ -116,25 +121,39 @@ class RSIStrategy(Strategy):
|
|
|
116
121
|
self.higher_threshold = higher_threshold
|
|
117
122
|
super().__init__(executor)
|
|
118
123
|
|
|
119
|
-
def execute(self, candle_df:
|
|
120
|
-
candle_df["
|
|
124
|
+
def execute(self, candle_df: PandasDataFrame) -> None:
|
|
125
|
+
candle_df["momentum_rsi"] = RSIIndicator(candle_df.close).rsi()
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
previous_candle = candle_df.iloc[-2]
|
|
128
|
+
current_candle = candle_df.iloc[-1]
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
is_buy_signal = (
|
|
131
|
+
previous_candle.momentum_rsi > self.lower_threshold
|
|
132
|
+
and current_candle.momentum_rsi < self.lower_threshold
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if is_buy_signal:
|
|
127
136
|
self.executor.place_market_buy_order(
|
|
128
137
|
self.executor.state.base,
|
|
129
138
|
self.executor.state.quote_available,
|
|
130
139
|
)
|
|
131
140
|
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
is_sell_signal = (
|
|
142
|
+
previous_candle.momentum_rsi < self.higher_threshold
|
|
143
|
+
and current_candle.momentum_rsi > self.higher_threshold
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if is_sell_signal:
|
|
134
147
|
self.executor.place_market_sell_order(
|
|
135
148
|
self.executor.state.base,
|
|
136
149
|
self.executor.state.base_available,
|
|
137
150
|
)
|
|
151
|
+
|
|
152
|
+
def get_data_pipeline(self) -> DataPipeline:
|
|
153
|
+
"""
|
|
154
|
+
Implement this metho
|
|
155
|
+
"""
|
|
156
|
+
return NoDataPipeline()
|
|
138
157
|
```
|
|
139
158
|
|
|
140
159
|
### 2. fetch data for your backtest
|
|
@@ -143,8 +162,9 @@ Market data is provided as a pandas DataFrame containing OHLCV candles.
|
|
|
143
162
|
```python
|
|
144
163
|
from dijkies.exchange_market_api import BitvavoMarketAPI
|
|
145
164
|
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
bitvavo_market_api = BitvavoMarketAPI()
|
|
166
|
+
|
|
167
|
+
candle_df = bitvavo_market_api.get_candles()
|
|
148
168
|
```
|
|
149
169
|
|
|
150
170
|
### 3. Set Up State and BacktestingExecutor
|
|
@@ -179,13 +199,129 @@ strategy = RSIStrategy(
|
|
|
179
199
|
higher_threshold=65,
|
|
180
200
|
)
|
|
181
201
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
results = backtester.run(
|
|
202
|
+
results = strategy.backtest(
|
|
185
203
|
candle_df=candle_df,
|
|
186
|
-
strategy=strategy,
|
|
187
204
|
)
|
|
188
205
|
|
|
189
206
|
results.total_value_strategy.plot()
|
|
190
207
|
results.total_value_hodl.plot()
|
|
191
|
-
```
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Deployment & Live Trading
|
|
211
|
+
|
|
212
|
+
Dijkies supports deploying strategies to live trading environments using the **same strategy code** that is used for backtesting. Deployment is built around a small set of composable components that handle persistence, credentials, execution switching, and bot lifecycle management.
|
|
213
|
+
|
|
214
|
+
At a high level, deployment works by:
|
|
215
|
+
|
|
216
|
+
1. Persisting a configured strategy
|
|
217
|
+
2. Attaching a live exchange executor
|
|
218
|
+
3. Running the strategy via a `Bot`
|
|
219
|
+
4. Managing lifecycle states such as *active*, *paused*, and *stopped*
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Core Deployment Concepts
|
|
224
|
+
|
|
225
|
+
### Strategy Persistence
|
|
226
|
+
|
|
227
|
+
Strategies are **serialized and stored** so they can be resumed, paused, or stopped without losing state.
|
|
228
|
+
|
|
229
|
+
This includes:
|
|
230
|
+
- Strategy parameters
|
|
231
|
+
- Internal indicators or buffers
|
|
232
|
+
- Account state (balances, open orders, etc.)
|
|
233
|
+
|
|
234
|
+
Persistence is handled through a `StrategyRepository`.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### Strategy Status
|
|
239
|
+
|
|
240
|
+
Each deployed strategy (bot) exists in one of the following states:
|
|
241
|
+
|
|
242
|
+
- **active** — strategy is running normally
|
|
243
|
+
- **paused** — strategy execution stopped due to an error
|
|
244
|
+
- **stopped** — strategy has been intentionally stopped
|
|
245
|
+
|
|
246
|
+
Status transitions are managed automatically by the deployment system.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### Executor Switching
|
|
251
|
+
|
|
252
|
+
One of Dijkies’ key design goals is that **strategies do not know whether they are backtesting or live trading**.
|
|
253
|
+
|
|
254
|
+
At deployment time, the executor is injected dynamically:
|
|
255
|
+
|
|
256
|
+
- `BacktestExchangeAssetClient` for backtesting
|
|
257
|
+
- `BitvavoExchangeAssetClient` for live trading
|
|
258
|
+
|
|
259
|
+
No strategy code changes are required.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Strategy Repository
|
|
264
|
+
|
|
265
|
+
The `StrategyRepository` abstraction defines how strategies are stored and retrieved.
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
class StrategyRepository(ABC):
|
|
269
|
+
def store(...)
|
|
270
|
+
def read(...)
|
|
271
|
+
def change_status(...)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### LocalStrategyRepository
|
|
275
|
+
|
|
276
|
+
The provided implementation stores strategies locally using pickle.
|
|
277
|
+
|
|
278
|
+
#### Directory Structure
|
|
279
|
+
|
|
280
|
+
root/
|
|
281
|
+
└── person_id/
|
|
282
|
+
└── exchange/
|
|
283
|
+
└── status/
|
|
284
|
+
└── bot_id.pkl
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
from pathlib import Path
|
|
288
|
+
from dijkies.bot import LocalStrategyRepository
|
|
289
|
+
|
|
290
|
+
repo = LocalStrategyRepository(Path("./strategies"))
|
|
291
|
+
|
|
292
|
+
# read
|
|
293
|
+
|
|
294
|
+
strategy = repo.read(
|
|
295
|
+
person_id="ArnoldDijk",
|
|
296
|
+
exchange="bitvavo",
|
|
297
|
+
bot_id="rsi_bot",
|
|
298
|
+
status="active"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# store
|
|
302
|
+
|
|
303
|
+
repo.store(
|
|
304
|
+
strategy=strategy,
|
|
305
|
+
person_id="ArnoldDijk",
|
|
306
|
+
exchange="bitvavo",
|
|
307
|
+
bot_id="berend_botje",
|
|
308
|
+
status="active"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# change status
|
|
312
|
+
|
|
313
|
+
repo.change_status(
|
|
314
|
+
person_id="ArnoldDijk",
|
|
315
|
+
exchange="bitvavo",
|
|
316
|
+
bot_id="berend_botje",
|
|
317
|
+
from_status="active",
|
|
318
|
+
to_status="stopped",
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
This makes it easy to:
|
|
323
|
+
|
|
324
|
+
- Resume bots after restarts
|
|
325
|
+
- Inspect stored strategies
|
|
326
|
+
- Build higher-level orchestration around the filesystem
|
|
327
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
dijkies/__init__.py,sha256=FmmEOqu9R1gE1wBWWIrAIFTF1rI5sEjpsCdrw4RC5Z8,167
|
|
2
|
+
dijkies/data_pipeline.py,sha256=-0557jmH4o4jghDiQzYVU3hzRXOKejIDFpcJWhcW-GY,1395
|
|
3
|
+
dijkies/deployment.py,sha256=8T4b91TRCcZCFe92gIqoCbk0vetbuhrORmDHq6M_5x8,7138
|
|
4
|
+
dijkies/exceptions.py,sha256=Sgbzm3GXSTfKnGf9BMEOjg7ksebjSIg53DqGU6KOUmg,2418
|
|
5
|
+
dijkies/exchange_market_api.py,sha256=RqA59y0f3O8GB8Za9DXkyH5XocVG0wiKuxJ7tRNijkg,7602
|
|
6
|
+
dijkies/executors.py,sha256=oPK62s11uCYooOizm8sNB0oJLevHDHnfAsOa5QNWlig,19386
|
|
7
|
+
dijkies/logger.py,sha256=yrxNyzriXO_WITRsIEWkVQeqLRavHVklFainDO18p7Y,432
|
|
8
|
+
dijkies/performance.py,sha256=bqY3d9-F1NtATwO7FK9JsD-MThEAqLsmWt_ty5Fowzc,5414
|
|
9
|
+
dijkies/strategy.py,sha256=5KJJuBA6_cwD6CZ4_c3_LSJXfMR_wphQ0S9sIv0eXRo,4242
|
|
10
|
+
dijkies-0.1.1.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
11
|
+
dijkies-0.1.1.dist-info/METADATA,sha256=xfWlwdi42OU2aTb3s1MuHG1yXwqGvQ8swP7PKoLLGKI,8881
|
|
12
|
+
dijkies-0.1.1.dist-info/RECORD,,
|
dijkies/backtest.py
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, timedelta
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
import pandas as pd
|
|
5
|
-
from pandas.core.frame import DataFrame as PandasDataFrame
|
|
6
|
-
|
|
7
|
-
from dijkies.evaluate import EvaluationFramework
|
|
8
|
-
from dijkies.executors import BacktestExchangeAssetClient
|
|
9
|
-
from dijkies.performance import PerformanceInformationRow
|
|
10
|
-
from dijkies.exceptions import (
|
|
11
|
-
DataTimeWindowShorterThanSuggestedAnalysisWindowError,
|
|
12
|
-
InvalidExchangeAssetClientError,
|
|
13
|
-
InvalidTypeForTimeColumnError,
|
|
14
|
-
MissingOHLCVColumnsError,
|
|
15
|
-
TimeColumnNotDefinedError,
|
|
16
|
-
)
|
|
17
|
-
from dijkies.strategy import Strategy
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Backtester:
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
evaluation: Optional[EvaluationFramework] = None,
|
|
24
|
-
):
|
|
25
|
-
self.evaluation = evaluation
|
|
26
|
-
|
|
27
|
-
@staticmethod
|
|
28
|
-
def get_analysis_df(
|
|
29
|
-
data: PandasDataFrame, current_time: datetime, look_back_in_min: int
|
|
30
|
-
) -> PandasDataFrame:
|
|
31
|
-
start_analysis_df = current_time - timedelta(minutes=look_back_in_min)
|
|
32
|
-
|
|
33
|
-
analysis_df = data.loc[
|
|
34
|
-
(data.time >= start_analysis_df) & (data.time <= current_time)
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
return analysis_df
|
|
38
|
-
|
|
39
|
-
def simulate(
|
|
40
|
-
self,
|
|
41
|
-
data: PandasDataFrame,
|
|
42
|
-
strategy: Strategy,
|
|
43
|
-
) -> PandasDataFrame:
|
|
44
|
-
"""
|
|
45
|
-
This method runs the backtest. It expects data, this should have the following properties:
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
# validate args
|
|
49
|
-
|
|
50
|
-
if "time" not in data.columns:
|
|
51
|
-
raise TimeColumnNotDefinedError()
|
|
52
|
-
|
|
53
|
-
if not pd.api.types.is_datetime64_any_dtype(data.time):
|
|
54
|
-
raise InvalidTypeForTimeColumnError()
|
|
55
|
-
|
|
56
|
-
lookback_in_min = strategy.analysis_dataframe_size_in_minutes
|
|
57
|
-
timespan_data_in_min = (data.time.max() - data.time.min()).total_seconds() / 60
|
|
58
|
-
|
|
59
|
-
if lookback_in_min > timespan_data_in_min:
|
|
60
|
-
raise DataTimeWindowShorterThanSuggestedAnalysisWindowError()
|
|
61
|
-
|
|
62
|
-
if not {"open", "high", "low", "close", "volume"}.issubset(data.columns):
|
|
63
|
-
raise MissingOHLCVColumnsError()
|
|
64
|
-
|
|
65
|
-
if not isinstance(strategy.executor, BacktestExchangeAssetClient):
|
|
66
|
-
raise InvalidExchangeAssetClientError()
|
|
67
|
-
|
|
68
|
-
start_time = data.iloc[0].time + timedelta(minutes=lookback_in_min)
|
|
69
|
-
simulation_df: PandasDataFrame = data.loc[data.time >= start_time]
|
|
70
|
-
start_candle = simulation_df.iloc[0]
|
|
71
|
-
start_value_in_quote = strategy.state.total_value_in_quote(start_candle.open)
|
|
72
|
-
result = []
|
|
73
|
-
|
|
74
|
-
for _, candle in simulation_df.iterrows():
|
|
75
|
-
analysis_df = self.get_analysis_df(data, candle.time, lookback_in_min)
|
|
76
|
-
strategy.executor.update_current_candle(candle)
|
|
77
|
-
|
|
78
|
-
strategy.run(analysis_df)
|
|
79
|
-
|
|
80
|
-
result.append(
|
|
81
|
-
PerformanceInformationRow.from_objects(
|
|
82
|
-
candle, start_candle, strategy.state, start_value_in_quote
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
return pd.DataFrame([r.dict() for r in result])
|
|
87
|
-
|
|
88
|
-
def run(
|
|
89
|
-
self,
|
|
90
|
-
candle_df: PandasDataFrame,
|
|
91
|
-
strategy: Strategy,
|
|
92
|
-
) -> PandasDataFrame:
|
|
93
|
-
|
|
94
|
-
results = self.simulate(candle_df, strategy)
|
|
95
|
-
if isinstance(self.evaluation, EvaluationFramework):
|
|
96
|
-
self.evaluation.evaluate(results)
|
|
97
|
-
|
|
98
|
-
return results
|
dijkies/credentials.py
DELETED
dijkies/evaluate.py
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
import tempfile
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
from datetime import datetime, timezone
|
|
6
|
-
from typing import Optional, Union
|
|
7
|
-
|
|
8
|
-
import matplotlib.pyplot as plt
|
|
9
|
-
import mlflow
|
|
10
|
-
import pandas as pd
|
|
11
|
-
from pandas.core.frame import DataFrame as PandasDataFrame
|
|
12
|
-
|
|
13
|
-
from dijkies.performance import Metric
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class EvaluationFramework(ABC):
|
|
17
|
-
@abstractmethod
|
|
18
|
-
def evaluate(self, performance_results: PandasDataFrame) -> None:
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class MLFlowEvaluator(ABC):
|
|
23
|
-
@abstractmethod
|
|
24
|
-
def evaluate(self, performance_results: PandasDataFrame) -> None:
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class MLFlowEvaluationFramework(EvaluationFramework):
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
evaluators: list[MLFlowEvaluator],
|
|
32
|
-
experiment_name: str,
|
|
33
|
-
logger: logging.Logger,
|
|
34
|
-
strategy_parameters: Optional[dict[str, Union[int, str, float, bool]]],
|
|
35
|
-
log_dataset: bool = False,
|
|
36
|
-
) -> None:
|
|
37
|
-
self.evaluators = evaluators
|
|
38
|
-
self.logger = logger
|
|
39
|
-
self.experiment_name = experiment_name
|
|
40
|
-
self.log_dataset = log_dataset
|
|
41
|
-
self.strategy_parameters = strategy_parameters
|
|
42
|
-
|
|
43
|
-
def evaluate(self, performance_results: PandasDataFrame) -> None:
|
|
44
|
-
mlflow.set_experiment(self.experiment_name)
|
|
45
|
-
# for results:
|
|
46
|
-
# poetry run mlflow server --host 127.0.0.1 --port 8080
|
|
47
|
-
|
|
48
|
-
run_name = "run__" + datetime.now(tz=timezone.utc).strftime("%Y_%m_%d_%H_%M%Z")
|
|
49
|
-
|
|
50
|
-
with mlflow.start_run(run_name=run_name) as run:
|
|
51
|
-
run_id = run.info.run_id
|
|
52
|
-
self.logger.info("Run created: " + run_id)
|
|
53
|
-
if self.log_dataset:
|
|
54
|
-
dataset = mlflow.data.from_pandas(
|
|
55
|
-
performance_results, source="local", name="training_data"
|
|
56
|
-
)
|
|
57
|
-
mlflow.log_input(dataset, context="training")
|
|
58
|
-
mlflow.log_params(self.strategy_parameters)
|
|
59
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
60
|
-
file_path = os.path.join(tmpdir, "data.csv")
|
|
61
|
-
performance_results.to_csv(file_path, index=False)
|
|
62
|
-
mlflow.log_artifact(file_path)
|
|
63
|
-
|
|
64
|
-
[
|
|
65
|
-
evaluator.evaluate(performance_results) for evaluator in self.evaluators
|
|
66
|
-
] # type: ignore
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class MLFlowOverallEvaluator(MLFlowEvaluator):
|
|
70
|
-
def __init__(self, metrics: list[Metric], logger: logging.Logger) -> None:
|
|
71
|
-
self.metrics = metrics
|
|
72
|
-
self.logger = logger
|
|
73
|
-
|
|
74
|
-
def log_metrics(self, performance_results: PandasDataFrame) -> None:
|
|
75
|
-
for metric in self.metrics:
|
|
76
|
-
mlflow.log_metric(
|
|
77
|
-
"strategy_" + metric.metric_name,
|
|
78
|
-
round(metric.calculate(performance_results.total_value_strategy), 2),
|
|
79
|
-
)
|
|
80
|
-
mlflow.log_metric(
|
|
81
|
-
"hodl_" + metric.metric_name,
|
|
82
|
-
round(metric.calculate(performance_results.total_value_hodl), 2),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
@staticmethod
|
|
86
|
-
def plot_fee(performance_results: PandasDataFrame) -> None:
|
|
87
|
-
plt.figure(figsize=(8, 5))
|
|
88
|
-
plt.plot(
|
|
89
|
-
performance_results.candle_time,
|
|
90
|
-
performance_results.total_fee_paid,
|
|
91
|
-
color="blue",
|
|
92
|
-
label="fee",
|
|
93
|
-
)
|
|
94
|
-
plt.xlabel("Time")
|
|
95
|
-
plt.xticks(rotation=45)
|
|
96
|
-
plt.ylabel("fee paid in €")
|
|
97
|
-
plt.title("total transaction fee paid to Exchange")
|
|
98
|
-
plt.grid(True)
|
|
99
|
-
plt.legend()
|
|
100
|
-
|
|
101
|
-
# Log figure directly to MLflow
|
|
102
|
-
mlflow.log_figure(plt.gcf(), "total_fee_paid.png")
|
|
103
|
-
|
|
104
|
-
plt.close() # free memory
|
|
105
|
-
|
|
106
|
-
@staticmethod
|
|
107
|
-
def plot_balance_fractions(performance_results: PandasDataFrame) -> None:
|
|
108
|
-
perc_in_quote = (
|
|
109
|
-
performance_results.balance_total_quote
|
|
110
|
-
/ performance_results.total_value_strategy
|
|
111
|
-
)
|
|
112
|
-
perc_in_base = (
|
|
113
|
-
performance_results.balance_total_base
|
|
114
|
-
/ performance_results.total_value_strategy
|
|
115
|
-
* performance_results.candle_close
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
plt.figure(figsize=(8, 5))
|
|
119
|
-
plt.plot(
|
|
120
|
-
performance_results.candle_time,
|
|
121
|
-
perc_in_quote,
|
|
122
|
-
color="blue",
|
|
123
|
-
label="percentage value in quote",
|
|
124
|
-
)
|
|
125
|
-
plt.plot(
|
|
126
|
-
performance_results.candle_time,
|
|
127
|
-
perc_in_base,
|
|
128
|
-
color="red",
|
|
129
|
-
label="percentage value in base",
|
|
130
|
-
)
|
|
131
|
-
plt.xlabel("Time")
|
|
132
|
-
plt.xticks(rotation=45)
|
|
133
|
-
plt.ylabel("fraction")
|
|
134
|
-
plt.title("fraction of total value in quote")
|
|
135
|
-
plt.grid(True)
|
|
136
|
-
plt.legend()
|
|
137
|
-
|
|
138
|
-
# Log figure directly to MLflow
|
|
139
|
-
mlflow.log_figure(plt.gcf(), "balance_fractions.png")
|
|
140
|
-
|
|
141
|
-
plt.close() # free memory
|
|
142
|
-
|
|
143
|
-
@staticmethod
|
|
144
|
-
def plot_strategy_vs_hodl(performance_results: PandasDataFrame) -> None:
|
|
145
|
-
plt.figure(figsize=(8, 5))
|
|
146
|
-
plt.plot(
|
|
147
|
-
performance_results.candle_time,
|
|
148
|
-
performance_results.total_value_strategy,
|
|
149
|
-
color="blue",
|
|
150
|
-
label="strategy",
|
|
151
|
-
)
|
|
152
|
-
plt.plot(
|
|
153
|
-
performance_results.candle_time,
|
|
154
|
-
performance_results.total_value_hodl,
|
|
155
|
-
color="red",
|
|
156
|
-
label="hodl",
|
|
157
|
-
)
|
|
158
|
-
plt.xlabel("Time")
|
|
159
|
-
plt.xticks(rotation=45)
|
|
160
|
-
plt.ylabel("Value")
|
|
161
|
-
plt.title("strategy vs. hodl")
|
|
162
|
-
plt.grid(True)
|
|
163
|
-
plt.legend()
|
|
164
|
-
|
|
165
|
-
# Log figure directly to MLflow
|
|
166
|
-
mlflow.log_figure(plt.gcf(), "overal_result.png")
|
|
167
|
-
|
|
168
|
-
plt.close() # free memory
|
|
169
|
-
|
|
170
|
-
def plot_results(self, performance_results: PandasDataFrame) -> None:
|
|
171
|
-
self.plot_strategy_vs_hodl(performance_results)
|
|
172
|
-
self.plot_fee(performance_results)
|
|
173
|
-
self.plot_balance_fractions(performance_results)
|
|
174
|
-
|
|
175
|
-
def evaluate(self, performance_results: PandasDataFrame) -> None:
|
|
176
|
-
self.log_metrics(performance_results)
|
|
177
|
-
self.plot_results(performance_results)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class MLFlowSliceEvaluator(MLFlowEvaluator):
|
|
181
|
-
def __init__(
|
|
182
|
-
self, window_size_in_min: int, metrics: list[Metric], logger: logging.Logger
|
|
183
|
-
) -> None:
|
|
184
|
-
self.window_size_in_min = window_size_in_min
|
|
185
|
-
self.metrics = metrics
|
|
186
|
-
self.logger = logger
|
|
187
|
-
|
|
188
|
-
def results_window_slicer(self, results: PandasDataFrame) -> PandasDataFrame:
|
|
189
|
-
candle_interval_in_minutes = (
|
|
190
|
-
results.iloc[1].candle_time - results.iloc[0].candle_time
|
|
191
|
-
).total_seconds() / 60
|
|
192
|
-
window_size = self.window_size_in_min / candle_interval_in_minutes
|
|
193
|
-
|
|
194
|
-
evaluation = []
|
|
195
|
-
|
|
196
|
-
for sub_result in [
|
|
197
|
-
results.loc[i : i + window_size]
|
|
198
|
-
for i in range(len(results) - (int(window_size) + 1))
|
|
199
|
-
]:
|
|
200
|
-
row = {}
|
|
201
|
-
for metric in self.metrics:
|
|
202
|
-
row["strategy_" + metric.metric_name] = metric.calculate(
|
|
203
|
-
sub_result.total_value_strategy
|
|
204
|
-
)
|
|
205
|
-
row["hodl_" + metric.metric_name] = metric.calculate(
|
|
206
|
-
sub_result.total_value_hodl
|
|
207
|
-
)
|
|
208
|
-
evaluation.append(row)
|
|
209
|
-
|
|
210
|
-
return pd.DataFrame(evaluation)
|
|
211
|
-
|
|
212
|
-
def plot_results(self, slicer_results: PandasDataFrame) -> None:
|
|
213
|
-
for col in slicer_results.columns:
|
|
214
|
-
self.logger.info(f"create plot {col}")
|
|
215
|
-
plt.figure(figsize=(8, 5))
|
|
216
|
-
plt.hist(slicer_results[col])
|
|
217
|
-
plt.title(f"Column: {col}")
|
|
218
|
-
plt.xlabel(col)
|
|
219
|
-
plt.grid(True)
|
|
220
|
-
|
|
221
|
-
# Log figure directly to MLflow
|
|
222
|
-
mlflow.log_figure(plt.gcf(), f"{col}.png")
|
|
223
|
-
|
|
224
|
-
plt.close() # free memory
|
|
225
|
-
|
|
226
|
-
def log_metrics(self, slicer_results: PandasDataFrame) -> None:
|
|
227
|
-
for col in slicer_results.columns:
|
|
228
|
-
self.logger.info(f"compute metrics for {col}")
|
|
229
|
-
mlflow.log_metric(f"{col}_mean", round(slicer_results[col].mean(), 3))
|
|
230
|
-
mlflow.log_metric(f"{col}_std", round(slicer_results[col].std(), 3))
|
|
231
|
-
|
|
232
|
-
def evaluate(self, performance_results: PandasDataFrame) -> None:
|
|
233
|
-
slicer_results = self.results_window_slicer(performance_results)
|
|
234
|
-
self.log_metrics(slicer_results)
|
|
235
|
-
self.plot_results(slicer_results)
|
dijkies-0.0.3.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
dijkies/__init__.py,sha256=FmmEOqu9R1gE1wBWWIrAIFTF1rI5sEjpsCdrw4RC5Z8,167
|
|
2
|
-
dijkies/backtest.py,sha256=Z3U8pijdNwGh_h4dlPtORk3znIOBlTKOaGR-IK-swys,3206
|
|
3
|
-
dijkies/credentials.py,sha256=pwhRsTFnaDOinnRwnplnxAIDS778dPd2awFDmjo3dpY,104
|
|
4
|
-
dijkies/data_pipeline.py,sha256=SK5NCpI5okhwiPOAPTzoDVLgqPH6SGPsT4khNXcf7jI,313
|
|
5
|
-
dijkies/deployment.py,sha256=N8xtF2YTU1wA0RAcwVmJEaXKZnNH94XjutIs7aOFyYo,3638
|
|
6
|
-
dijkies/evaluate.py,sha256=52H87f4E7-1eTDN2CLSKkswtydTIeDSMZdCoXo56woA,8064
|
|
7
|
-
dijkies/exceptions.py,sha256=BLqK0-rmpqx3wtZ8abu19u8ClnsAjkcpz4B22p-YYt0,1907
|
|
8
|
-
dijkies/exchange_market_api.py,sha256=ZH2gD_Z7PkX3KLT1GtG20Xr-bacj4B3IGsJNP7Hz5AE,7606
|
|
9
|
-
dijkies/executors.py,sha256=7GJznhlv2WMP6UjqQeE6s9VQsvzD0Y0okXdJd4rmMdk,20896
|
|
10
|
-
dijkies/logger.py,sha256=yrxNyzriXO_WITRsIEWkVQeqLRavHVklFainDO18p7Y,432
|
|
11
|
-
dijkies/performance.py,sha256=1EoGx0_riiAyOP8G7UQcYwmjt8b8hw4c3UzAgsbpOuE,5416
|
|
12
|
-
dijkies/strategy.py,sha256=4UZpuZcDvtol6RbarUw-K846bZritSDqDirz9w8ycPw,1804
|
|
13
|
-
dijkies-0.0.3.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
|
|
14
|
-
dijkies-0.0.3.dist-info/METADATA,sha256=zgh4Zr4w3-yFTLg0le_rKDHMIsMpltd68ZT9AAkTx1w,5717
|
|
15
|
-
dijkies-0.0.3.dist-info/RECORD,,
|