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
dijkies/data_pipeline.py
CHANGED
|
@@ -3,6 +3,8 @@ from abc import ABC, abstractmethod
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
from pandas.core.frame import DataFrame as PandasDataFrame
|
|
5
5
|
|
|
6
|
+
from dijkies.exchange_market_api import ExchangeMarketAPI
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class DataPipeline(ABC):
|
|
8
10
|
@abstractmethod
|
|
@@ -10,6 +12,35 @@ class DataPipeline(ABC):
|
|
|
10
12
|
pass
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
class NoDataPipeline(
|
|
15
|
+
class NoDataPipeline(DataPipeline):
|
|
16
|
+
def run(self) -> PandasDataFrame:
|
|
17
|
+
return pd.DataFrame([{}], columns=["close"])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CurrentValueDataPipeline(DataPipeline):
|
|
21
|
+
def __init__(self, base: str, exchange_market_api: ExchangeMarketAPI) -> None:
|
|
22
|
+
self.exchange_market_api = exchange_market_api
|
|
23
|
+
self.base = base
|
|
24
|
+
|
|
25
|
+
def run(self) -> PandasDataFrame:
|
|
26
|
+
current_price = self.exchange_market_api.get_price(self.base)
|
|
27
|
+
return pd.DataFrame([{"close": current_price}])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OHLCVDataPipeline(DataPipeline):
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
exchange_market_api: ExchangeMarketAPI,
|
|
34
|
+
base: str,
|
|
35
|
+
candle_interval_in_minutes: int,
|
|
36
|
+
lookback_in_minutes: int,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.exchange_market_api = exchange_market_api
|
|
39
|
+
self.base = base
|
|
40
|
+
self.candle_interval_in_minutes = candle_interval_in_minutes
|
|
41
|
+
self.lookback_in_minutes = lookback_in_minutes
|
|
42
|
+
|
|
14
43
|
def run(self) -> PandasDataFrame:
|
|
15
|
-
return
|
|
44
|
+
return self.exchange_market_api.get_candles(
|
|
45
|
+
self.base, self.candle_interval_in_minutes, self.lookback_in_minutes
|
|
46
|
+
)
|
dijkies/deployment.py
CHANGED
|
@@ -1,94 +1,140 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import json
|
|
3
2
|
import pickle
|
|
4
|
-
|
|
3
|
+
import shutil
|
|
5
4
|
from abc import ABC, abstractmethod
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from dijkies.
|
|
10
|
-
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from dijkies.executors import (
|
|
9
|
+
SUPPORTED_EXCHANGES,
|
|
10
|
+
BacktestExchangeAssetClient,
|
|
11
|
+
BitvavoExchangeAssetClient,
|
|
12
|
+
)
|
|
11
13
|
from dijkies.logger import get_logger
|
|
14
|
+
from dijkies.strategy import Strategy
|
|
12
15
|
|
|
16
|
+
BOT_STATUS = Literal["active", "paused", "stopped"]
|
|
17
|
+
ASSET_HANDLING = Literal["quote_only", "base_only", "ignore"]
|
|
13
18
|
|
|
14
|
-
class StrategyRepository(ABC):
|
|
15
|
-
@abstractmethod
|
|
16
|
-
def store(self, strategy: Strategy, id: str) -> None:
|
|
17
|
-
pass
|
|
18
19
|
|
|
20
|
+
class StrategyRepository(ABC):
|
|
19
21
|
@abstractmethod
|
|
20
|
-
def
|
|
22
|
+
def store(
|
|
23
|
+
self,
|
|
24
|
+
strategy: Strategy,
|
|
25
|
+
person_id: str,
|
|
26
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
27
|
+
bot_id: str,
|
|
28
|
+
status: BOT_STATUS,
|
|
29
|
+
) -> None:
|
|
21
30
|
pass
|
|
22
31
|
|
|
23
32
|
@abstractmethod
|
|
24
|
-
def read(
|
|
33
|
+
def read(
|
|
34
|
+
self,
|
|
35
|
+
person_id: str,
|
|
36
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
37
|
+
bot_id: str,
|
|
38
|
+
status: BOT_STATUS,
|
|
39
|
+
) -> Strategy:
|
|
25
40
|
pass
|
|
26
41
|
|
|
27
42
|
@abstractmethod
|
|
28
|
-
def
|
|
43
|
+
def change_status(
|
|
44
|
+
self,
|
|
45
|
+
person_id: str,
|
|
46
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
47
|
+
bot_id: str,
|
|
48
|
+
from_status: BOT_STATUS,
|
|
49
|
+
to_status: BOT_STATUS,
|
|
50
|
+
) -> None:
|
|
29
51
|
pass
|
|
30
52
|
|
|
31
53
|
|
|
32
54
|
class LocalStrategyRepository(StrategyRepository):
|
|
33
|
-
def __init__(self, root_directory:
|
|
55
|
+
def __init__(self, root_directory: Path) -> None:
|
|
34
56
|
self.root_directory = root_directory
|
|
35
57
|
|
|
36
|
-
def store(
|
|
37
|
-
|
|
58
|
+
def store(
|
|
59
|
+
self,
|
|
60
|
+
strategy: Strategy,
|
|
61
|
+
person_id: str,
|
|
62
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
63
|
+
bot_id: str,
|
|
64
|
+
status: BOT_STATUS,
|
|
65
|
+
) -> None:
|
|
66
|
+
(self.root_directory / person_id / exchange / status).mkdir(
|
|
67
|
+
parents=True, exist_ok=True
|
|
68
|
+
)
|
|
69
|
+
path = os.path.join(
|
|
70
|
+
self.root_directory, person_id, exchange, status, bot_id + ".pkl"
|
|
71
|
+
)
|
|
38
72
|
with open(path, "wb") as file:
|
|
39
73
|
pickle.dump(strategy, file)
|
|
40
74
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
def read(
|
|
76
|
+
self,
|
|
77
|
+
person_id: str,
|
|
78
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
79
|
+
bot_id: str,
|
|
80
|
+
status: BOT_STATUS,
|
|
81
|
+
) -> Strategy:
|
|
82
|
+
path = os.path.join(
|
|
83
|
+
self.root_directory, person_id, exchange, status, bot_id + ".pkl"
|
|
84
|
+
)
|
|
48
85
|
with open(path, "rb") as file:
|
|
49
86
|
strategy = pickle.load(file)
|
|
50
87
|
return strategy
|
|
51
88
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
def change_status(
|
|
90
|
+
self,
|
|
91
|
+
person_id: str,
|
|
92
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
93
|
+
bot_id: str,
|
|
94
|
+
from_status: BOT_STATUS,
|
|
95
|
+
to_status: BOT_STATUS,
|
|
96
|
+
) -> None:
|
|
97
|
+
if from_status == to_status:
|
|
98
|
+
return
|
|
99
|
+
src = (
|
|
100
|
+
Path(f"{self.root_directory}/{person_id}/{exchange}/{from_status}")
|
|
101
|
+
/ f"{bot_id}.pkl"
|
|
102
|
+
)
|
|
103
|
+
dest_folder = Path(f"{self.root_directory}/{person_id}/{exchange}/{to_status}")
|
|
104
|
+
|
|
105
|
+
dest_folder.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
shutil.move(src, dest_folder / src.name)
|
|
57
107
|
|
|
58
108
|
|
|
59
109
|
class CredentialsRepository(ABC):
|
|
60
110
|
@abstractmethod
|
|
61
|
-
def get_api_key(self,
|
|
111
|
+
def get_api_key(self, person_id: str, exchange: str) -> str:
|
|
62
112
|
pass
|
|
63
113
|
|
|
64
114
|
@abstractmethod
|
|
65
|
-
def store_api_key(self,
|
|
115
|
+
def store_api_key(self, person_id: str, exchange: str, api_key: str) -> None:
|
|
66
116
|
pass
|
|
67
117
|
|
|
68
118
|
@abstractmethod
|
|
69
|
-
def get_api_secret_key(self,
|
|
119
|
+
def get_api_secret_key(self, person_id: str, exchange: str) -> str:
|
|
70
120
|
pass
|
|
71
121
|
|
|
72
122
|
@abstractmethod
|
|
73
|
-
def store_api_secret_key(
|
|
123
|
+
def store_api_secret_key(
|
|
124
|
+
self, person_id: str, exchange: str, api_secret_key: str
|
|
125
|
+
) -> None:
|
|
74
126
|
pass
|
|
75
127
|
|
|
76
|
-
def get_credentials(self, id: str) -> Credentials:
|
|
77
|
-
return Credentials(
|
|
78
|
-
api_key=self.get_api_key(id),
|
|
79
|
-
api_secret_key=self.get_api_secret_key(id)
|
|
80
|
-
)
|
|
81
|
-
|
|
82
128
|
|
|
83
|
-
class
|
|
84
|
-
def get_api_key(self,
|
|
85
|
-
return os.environ.get(f"{
|
|
129
|
+
class LocalCredentialsRepository(CredentialsRepository):
|
|
130
|
+
def get_api_key(self, person_id: str, exchange: str) -> str:
|
|
131
|
+
return os.environ.get(f"{person_id}_{exchange}_api_key")
|
|
86
132
|
|
|
87
|
-
def store_api_key(self,
|
|
133
|
+
def store_api_key(self, person_id: str, exchange: str, api_key: str) -> None:
|
|
88
134
|
pass
|
|
89
135
|
|
|
90
|
-
def get_api_secret_key(self,
|
|
91
|
-
return os.environ.get(f"{
|
|
136
|
+
def get_api_secret_key(self, person_id: str, exchange: str) -> str:
|
|
137
|
+
return os.environ.get(f"{person_id}_{exchange}_api_secret_key")
|
|
92
138
|
|
|
93
139
|
def store_api_secret_key(self, id: str, api_secret_key: str) -> None:
|
|
94
140
|
pass
|
|
@@ -103,25 +149,92 @@ class Bot:
|
|
|
103
149
|
self.strategy_repository = strategy_repository
|
|
104
150
|
self.credential_repository = credential_repository
|
|
105
151
|
|
|
106
|
-
def set_executor(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
152
|
+
def set_executor(
|
|
153
|
+
self,
|
|
154
|
+
strategy: Strategy,
|
|
155
|
+
person_id: str,
|
|
156
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
157
|
+
) -> None:
|
|
158
|
+
if exchange == "bitvavo":
|
|
159
|
+
api_key = self.credential_repository.get_api_key(person_id, exchange)
|
|
160
|
+
api_secret_key = self.credential_repository.get_api_secret_key(
|
|
161
|
+
person_id, exchange
|
|
162
|
+
)
|
|
111
163
|
strategy.executor = BitvavoExchangeAssetClient(
|
|
112
|
-
strategy.state,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
164
|
+
strategy.state, api_key, api_secret_key, 1, get_logger()
|
|
165
|
+
)
|
|
166
|
+
elif exchange == "backtest":
|
|
167
|
+
strategy.executor = BacktestExchangeAssetClient(
|
|
168
|
+
strategy.state, 0.0025, 0.0015
|
|
117
169
|
)
|
|
170
|
+
else:
|
|
171
|
+
raise Exception("exchange not defined")
|
|
172
|
+
|
|
173
|
+
def run(
|
|
174
|
+
self,
|
|
175
|
+
person_id: str,
|
|
176
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
177
|
+
bot_id: str,
|
|
178
|
+
status: BOT_STATUS,
|
|
179
|
+
) -> None:
|
|
118
180
|
|
|
119
|
-
|
|
120
|
-
strategy
|
|
121
|
-
self.set_executor(strategy)
|
|
181
|
+
strategy = self.strategy_repository.read(person_id, exchange, bot_id, status)
|
|
182
|
+
self.set_executor(strategy, person_id, exchange)
|
|
122
183
|
|
|
123
184
|
data_pipeline = strategy.get_data_pipeline()
|
|
124
185
|
data = data_pipeline.run()
|
|
125
|
-
strategy.run(data)
|
|
126
186
|
|
|
127
|
-
|
|
187
|
+
try:
|
|
188
|
+
strategy.run(data)
|
|
189
|
+
self.strategy_repository.store(
|
|
190
|
+
strategy, person_id, exchange, bot_id, status
|
|
191
|
+
)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
self.strategy_repository.store(
|
|
194
|
+
strategy, person_id, exchange, bot_id, status
|
|
195
|
+
)
|
|
196
|
+
self.strategy_repository.change_status(
|
|
197
|
+
person_id, exchange, bot_id, status, "paused"
|
|
198
|
+
)
|
|
199
|
+
raise Exception(e)
|
|
200
|
+
|
|
201
|
+
def stop(
|
|
202
|
+
self,
|
|
203
|
+
person_id: str,
|
|
204
|
+
exchange: SUPPORTED_EXCHANGES,
|
|
205
|
+
bot_id: str,
|
|
206
|
+
status: BOT_STATUS,
|
|
207
|
+
asset_handling: ASSET_HANDLING,
|
|
208
|
+
) -> None:
|
|
209
|
+
if status == "stopped":
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
strategy = self.strategy_repository.read(person_id, exchange, bot_id, status)
|
|
213
|
+
self.set_executor(strategy, person_id, exchange)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
for open_order in strategy.state.open_orders:
|
|
217
|
+
_ = strategy.executor.cancel_order(open_order)
|
|
218
|
+
if asset_handling == "base_only":
|
|
219
|
+
_ = strategy.executor.place_market_buy_order(
|
|
220
|
+
strategy.state.base, strategy.state.quote_available
|
|
221
|
+
)
|
|
222
|
+
elif asset_handling == "quote_only":
|
|
223
|
+
_ = strategy.executor.place_market_sell_order(
|
|
224
|
+
strategy.state.base, strategy.state.base_available
|
|
225
|
+
)
|
|
226
|
+
self.strategy_repository.store(
|
|
227
|
+
strategy, person_id, exchange, bot_id, status
|
|
228
|
+
)
|
|
229
|
+
self.strategy_repository.change_status(
|
|
230
|
+
person_id, exchange, bot_id, status, "stopped"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
self.strategy_repository.store(
|
|
235
|
+
strategy, person_id, exchange, bot_id, status
|
|
236
|
+
)
|
|
237
|
+
self.strategy_repository.change_status(
|
|
238
|
+
person_id, exchange, bot_id, status, "paused"
|
|
239
|
+
)
|
|
240
|
+
raise Exception(e)
|
dijkies/exceptions.py
CHANGED
|
@@ -58,3 +58,26 @@ class CancelOrderError(Exception):
|
|
|
58
58
|
class MethodNotDefinedError(Exception):
|
|
59
59
|
def __init__(self):
|
|
60
60
|
super().__init__("method not implemented...")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class InsufficientBalanceError(Exception):
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
balance: dict[str, float],
|
|
67
|
+
requested: float,
|
|
68
|
+
):
|
|
69
|
+
super().__init__(
|
|
70
|
+
f"""
|
|
71
|
+
not enough balance:\n
|
|
72
|
+
available: {balance["available"]}, requested: {requested}\n
|
|
73
|
+
"""
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class InsufficientOrderValueError(Exception):
|
|
78
|
+
def __init__(self):
|
|
79
|
+
super().__init__(
|
|
80
|
+
"""
|
|
81
|
+
order value should be at least 5 euro:
|
|
82
|
+
"""
|
|
83
|
+
)
|
dijkies/exchange_market_api.py
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
|
|
3
|
-
from pandas.core.frame import DataFrame as PandasDataFrame
|
|
4
|
-
|
|
5
1
|
import logging
|
|
6
|
-
|
|
7
|
-
import pandas as pd
|
|
8
|
-
import requests
|
|
9
|
-
from binance.client import Client
|
|
10
|
-
|
|
11
2
|
import threading
|
|
12
3
|
import time
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
13
5
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
14
6
|
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import requests
|
|
9
|
+
from binance.client import Client
|
|
10
|
+
from pandas.core.frame import DataFrame as PandasDataFrame
|
|
15
11
|
from python_bitvavo_api.bitvavo import Bitvavo
|
|
16
12
|
|
|
17
13
|
from dijkies.logger import get_logger
|