dijkies 0.0.3__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/performance.py ADDED
@@ -0,0 +1,166 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import numpy as np
4
+ from pandas.core.series import Series as PandasSeries
5
+
6
+ import uuid
7
+ from datetime import datetime
8
+
9
+ from pandas.core.series import Series
10
+ from pydantic import BaseModel
11
+
12
+ from dijkies.executors import State
13
+
14
+
15
+ class PerformanceInformationRow(BaseModel):
16
+ id: str = str(uuid.uuid4())
17
+ candle_time: datetime
18
+ candle_open: float
19
+ candle_high: float
20
+ candle_low: float
21
+ candle_close: float
22
+ buy_orders: list = []
23
+ sell_orders: list = []
24
+ balance_total_base: float
25
+ balance_total_quote: float
26
+ balance_available_base: float
27
+ balance_available_quote: float
28
+ balance_base_on_hold: float
29
+ balance_quote_on_hold: float
30
+ total_fee_paid: float
31
+ total_value_strategy: float
32
+ roi_strategy: float
33
+ roi_strategy_per_month: float
34
+ total_value_hodl: float
35
+ roi_hodl: float
36
+ roi_hodl_per_month: float
37
+ number_of_transactions: int
38
+ absolute_profit: float
39
+
40
+ @classmethod
41
+ def from_objects(
42
+ cls,
43
+ candle: Series,
44
+ start_candle: Series,
45
+ state: State,
46
+ initialization_value_in_quote: float,
47
+ ) -> "PerformanceInformationRow":
48
+ strategy_value = state.total_value_in_quote(candle.close)
49
+ hodl_value = (candle.close / start_candle.open) * initialization_value_in_quote
50
+
51
+ roi_strategy = ((strategy_value / initialization_value_in_quote) - 1) * 100
52
+ roi_hodl = ((candle.close / start_candle.open) - 1) * 100
53
+
54
+ duration_in_months = (candle.time - start_candle.time).total_seconds() / (
55
+ 60 * 60 * 24 * 30
56
+ )
57
+
58
+ strategy_per_month = (roi_strategy / 100 + 1) ** (
59
+ 1 / max(duration_in_months, 0.01)
60
+ )
61
+ hodl_per_month = (roi_hodl / 100 + 1) ** (1 / max(duration_in_months, 0.01))
62
+
63
+ return cls(
64
+ candle_time=candle.time,
65
+ candle_open=candle.open,
66
+ candle_high=candle.high,
67
+ candle_low=candle.low,
68
+ candle_close=candle.close,
69
+ buy_orders=state.buy_orders,
70
+ sell_orders=state.sell_orders,
71
+ balance_total_base=state.total_base,
72
+ balance_total_quote=state.total_quote,
73
+ balance_available_base=state.base_available,
74
+ balance_available_quote=state.quote_available,
75
+ balance_base_on_hold=state.base_on_hold,
76
+ balance_quote_on_hold=state.quote_on_hold,
77
+ total_fee_paid=state.total_fee_paid,
78
+ total_value_strategy=strategy_value,
79
+ roi_strategy=roi_strategy,
80
+ roi_strategy_per_month=strategy_per_month,
81
+ total_value_hodl=hodl_value,
82
+ roi_hodl=roi_hodl,
83
+ roi_hodl_per_month=hodl_per_month,
84
+ number_of_transactions=state.number_of_transactions,
85
+ absolute_profit=strategy_value - initialization_value_in_quote,
86
+ )
87
+
88
+
89
+ class Metric(ABC):
90
+ @property
91
+ @abstractmethod
92
+ def metric_name(self) -> str:
93
+ pass
94
+
95
+ @abstractmethod
96
+ def calculate(self, time_series: PandasSeries) -> float:
97
+ pass
98
+
99
+
100
+ class DrawDown(Metric):
101
+ @property
102
+ def metric_name(self) -> str:
103
+ return "draw_down"
104
+
105
+ def calculate(self, time_series: PandasSeries) -> float:
106
+ returns = time_series.pct_change()
107
+ returns.fillna(0.0, inplace=True)
108
+
109
+ cumulative = (returns + 1).cumprod()
110
+ running_max = np.maximum.accumulate(cumulative)
111
+
112
+ result = ((cumulative - running_max) / running_max).min() * 100
113
+
114
+ return result
115
+
116
+
117
+ class ReturnOnInvestment(Metric):
118
+ @property
119
+ def metric_name(self) -> str:
120
+ return "roi"
121
+
122
+ def calculate(self, time_series: PandasSeries) -> float:
123
+ return ((time_series.iloc[-1] / time_series.iloc[0]) - 1) * 100
124
+
125
+
126
+ class NormalizedReturnOnInvestment(Metric):
127
+ def __init__(self, candle_interval_in_minutes: int) -> None:
128
+ self.candle_interval_in_minutes = candle_interval_in_minutes
129
+
130
+ @property
131
+ def metric_name(self) -> str:
132
+ return "normalized_roi"
133
+
134
+ def calculate(self, time_series: PandasSeries) -> float:
135
+ """
136
+ returns the average return per month.
137
+ """
138
+ total_time_in_minutes = len(time_series) * self.candle_interval_in_minutes
139
+ total_time_in_months = total_time_in_minutes / (60 * 24 * 30)
140
+
141
+ total_return = time_series.iloc[-1] / time_series.iloc[0]
142
+ return_per_month = total_return ** (min(1 / total_time_in_months, 1))
143
+
144
+ return (return_per_month - 1) * 100
145
+
146
+
147
+ class SharpeRatio(Metric):
148
+ def __init__(
149
+ self, risk_free_rate_per_year: float, candle_interval_in_minutes: int
150
+ ) -> None:
151
+ self.risk_free_rate_per_year = risk_free_rate_per_year
152
+ self.candle_interval_in_minutes = candle_interval_in_minutes
153
+
154
+ @property
155
+ def metric_name(self) -> str:
156
+ return "sharpe_ratio"
157
+
158
+ def calculate(self, time_series: PandasSeries) -> float:
159
+ risk_free_rate_per_day = (1 + self.risk_free_rate_per_year) ** (1 / 365) - 1
160
+ measurements_per_day = 60 * 24 / self.candle_interval_in_minutes
161
+ normalized_std_return = risk_free_rate_per_day / max(measurements_per_day, 1)
162
+ returns = time_series.pct_change()
163
+ excess_return = returns.mean() - normalized_std_return
164
+ volatility = max(returns.std(), 0.00001)
165
+ sharpe_ratio = excess_return / volatility
166
+ return sharpe_ratio * np.sqrt(measurements_per_day)
dijkies/strategy.py ADDED
@@ -0,0 +1,68 @@
1
+ import inspect
2
+ from abc import ABC, abstractmethod
3
+
4
+ from pandas.core.frame import DataFrame as PandasDataFrame
5
+
6
+ from dijkies.executors import ExchangeAssetClient
7
+ from dijkies.data_pipeline import DataPipeline
8
+
9
+
10
+ class Strategy(ABC):
11
+ def __init__(
12
+ self,
13
+ executor: ExchangeAssetClient,
14
+ ) -> None:
15
+ self.executor = executor
16
+ self.state = self.executor.state
17
+
18
+ @abstractmethod
19
+ def execute(self, data: PandasDataFrame) -> None:
20
+ pass
21
+
22
+ def run(self, data: PandasDataFrame) -> None:
23
+ self.executor.update_state()
24
+ self.execute(data)
25
+
26
+ @classmethod
27
+ def _get_strategy_params(cls) -> list[str]:
28
+ subclass_sig = inspect.signature(cls.__init__)
29
+ base_sig = inspect.signature(Strategy.__init__)
30
+
31
+ subclass_params = {
32
+ name: p for name, p in subclass_sig.parameters.items() if name != "self"
33
+ }
34
+ base_params = {
35
+ name: p for name, p in base_sig.parameters.items() if name != "self"
36
+ }
37
+
38
+ unique_params = {
39
+ name: p for name, p in subclass_params.items() if name not in base_params
40
+ }
41
+
42
+ return list(unique_params.keys())
43
+
44
+ def params_to_json(self):
45
+ params = self._get_strategy_params()
46
+ return {p: getattr(self, p) for p in params}
47
+
48
+ def __getstate__(self):
49
+ state = self.__dict__.copy()
50
+ state["executor"] = None
51
+ state["data_pipeline"] = None
52
+ return state
53
+
54
+ @property
55
+ @abstractmethod
56
+ def analysis_dataframe_size_in_minutes(self) -> int:
57
+ pass
58
+
59
+ @property
60
+ @abstractmethod
61
+ def exchange(self) -> str:
62
+ pass
63
+
64
+ def get_data_pipeline(self) -> DataPipeline:
65
+ """
66
+ implement this method for deployement
67
+ """
68
+ raise NotImplementedError()
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.3
2
+ Name: dijkies
3
+ Version: 0.0.3
4
+ Summary: A python framework that can be used to create, test and deploy trading algorithms.
5
+ Author: Arnold Dijk
6
+ Author-email: Arnold Dijk <arnold.dijk@teamrockstars.nl>
7
+ License: MIT
8
+ Requires-Dist: mlflow>=3.7.0
9
+ Requires-Dist: pandas>=2.3.3
10
+ Requires-Dist: pre-commit>=4.5.0
11
+ Requires-Dist: pydantic>=2.12.5
12
+ Requires-Dist: pytest>=9.0.2
13
+ Requires-Dist: python-binance>=1.0.33
14
+ Requires-Dist: python-bitvavo-api>=1.4.3
15
+ Requires-Dist: ta>=0.11.0
16
+ Requires-Dist: tenacity>=9.1.2
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Dijkies
21
+
22
+ **Dijkies** is a Python framework for creating, testing, and deploying algorithmic trading strategies in a clean, modular, and exchange-agnostic way.
23
+
24
+ The core idea behind Dijkies is to **separate trading logic from execution and infrastructure**, allowing the same strategy code to be reused for:
25
+
26
+ - Historical backtesting
27
+ - Paper trading
28
+ - Live trading
29
+
30
+ ## Philosophy
31
+
32
+ In Dijkies, a strategy is responsible only for **making decisions** — when to buy, when to sell, and how much. Everything else, such as order execution, fee calculation, balance management, and exchange communication, is handled by dedicated components.
33
+
34
+ This separation ensures that strategies remain:
35
+
36
+ - Easy to reason about
37
+ - Easy to test
38
+ - Easy to reuse across environments
39
+
40
+ A strategy written once can be backtested on historical data and later deployed to a real exchange without modification.
41
+
42
+ ## How It Works
43
+
44
+ At a high level, Dijkies operates as follows:
45
+
46
+ 1. Market data (candles) is fetched from an exchange or data provider
47
+ 2. A rolling window of historical data is passed to a strategy
48
+ 3. The strategy analyzes the data and generates buy/sell signals
49
+ 4. Orders are placed through a standardized execution interface
50
+ 5. Account state is updated accordingly
51
+ 6. Results are collected (during backtesting) or executed live
52
+
53
+
54
+ ## Key Design Principles
55
+
56
+ - **Strategy–Executor separation**
57
+ Trading logic is completely decoupled from execution logic.
58
+
59
+ - **Single interface for backtesting and live trading**
60
+ Switching between backtesting and live trading requires no strategy changes.
61
+
62
+ - **Explicit state management**
63
+ All balances and positions are tracked in a transparent `State` object.
64
+
65
+ - **Minimal assumptions**
66
+ Dijkies does not enforce indicators, timeframes, or asset types.
67
+
68
+ - **Composable and extensible**
69
+ New exchanges, execution models, and risk layers can be added easily.
70
+
71
+ ## Who Is This For?
72
+
73
+ Dijkies is designed for:
74
+
75
+ - Developers building algorithmic trading systems
76
+ - Quantitative traders who want full control over strategy logic
77
+ - Anyone who wants to move from backtesting to production without rewriting code
78
+
79
+ ## What Dijkies Is Not
80
+
81
+ - A no-code trading bot
82
+ - A black-box strategy optimizer
83
+ - A fully managed trading platform
84
+
85
+ Dijkies provides the **building blocks**, not the trading edge.
86
+
87
+ ---
88
+
89
+ ## Quick Start
90
+
91
+ This quick start shows how to define a strategy, fetch market data, and run a backtest in just a few steps.
92
+
93
+ ### 1. Define a Strategy
94
+
95
+ A strategy is a class that inherits from `Strategy` and implements the `execute` method.
96
+ It receives a rolling dataframe of candles and decides when to place orders.
97
+
98
+ ```python
99
+ from dijkies.strategy import Strategy
100
+ from dijkies.executors import ExchangeAssetClient
101
+ from ta.momentum import RSIIndicator
102
+ import pandas as pd
103
+
104
+
105
+ class RSIStrategy(Strategy):
106
+ # Amount of historical data passed into execute()
107
+ analysis_dataframe_size_in_minutes = 60 * 24 * 30 # 30 days
108
+
109
+ def __init__(
110
+ self,
111
+ executor: ExchangeAssetClient,
112
+ lower_threshold: float,
113
+ higher_threshold: float,
114
+ ) -> None:
115
+ self.lower_threshold = lower_threshold
116
+ self.higher_threshold = higher_threshold
117
+ super().__init__(executor)
118
+
119
+ def execute(self, candle_df: pd.DataFrame) -> None:
120
+ candle_df["rsi"] = RSIIndicator(candle_df.close).rsi()
121
+
122
+ previous = candle_df.iloc[-2]
123
+ current = candle_df.iloc[-1]
124
+
125
+ # Buy when RSI crosses below lower threshold
126
+ if previous.rsi > self.lower_threshold and current.rsi < self.lower_threshold:
127
+ self.executor.place_market_buy_order(
128
+ self.executor.state.base,
129
+ self.executor.state.quote_available,
130
+ )
131
+
132
+ # Sell when RSI crosses above higher threshold
133
+ if previous.rsi < self.higher_threshold and current.rsi > self.higher_threshold:
134
+ self.executor.place_market_sell_order(
135
+ self.executor.state.base,
136
+ self.executor.state.base_available,
137
+ )
138
+ ```
139
+
140
+ ### 2. fetch data for your backtest
141
+ Market data is provided as a pandas DataFrame containing OHLCV candles.
142
+
143
+ ```python
144
+ from dijkies.exchange_market_api import BitvavoMarketAPI
145
+
146
+ market_api = BitvavoMarketAPI()
147
+ candle_df = market_api.get_candles(base="XRP", lookback_in_minutest=60*24*365)
148
+ ```
149
+
150
+ ### 3. Set Up State and BacktestingExecutor
151
+ Market data is provided as a pandas DataFrame containing OHLCV candles.
152
+
153
+ ```python
154
+ from dijkies.executors import BacktestExchangeAssetClient, State
155
+
156
+ state = State(
157
+ base="XRP",
158
+ total_base=0,
159
+ total_quote=1000,
160
+ )
161
+
162
+ executor = BacktestExchangeAssetClient(
163
+ state=state,
164
+ fee_limit_order=0.0015,
165
+ fee_market_order=0.0025,
166
+ )
167
+ ```
168
+
169
+ ### 4. Run the Backtest
170
+
171
+ Use the Backtester to run the strategy over historical data.
172
+
173
+ ```python
174
+ from dijkies.backtest import Backtester
175
+
176
+ strategy = RSIStrategy(
177
+ executor=executor,
178
+ lower_threshold=35,
179
+ higher_threshold=65,
180
+ )
181
+
182
+ backtester = Backtester()
183
+
184
+ results = backtester.run(
185
+ candle_df=candle_df,
186
+ strategy=strategy,
187
+ )
188
+
189
+ results.total_value_strategy.plot()
190
+ results.total_value_hodl.plot()
191
+ ```
@@ -0,0 +1,15 @@
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.18
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any