PyAlgoEngine 0.7.4__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.
- PyAlgoEngine-0.7.4.dist-info/LICENSE +21 -0
- PyAlgoEngine-0.7.4.dist-info/METADATA +27 -0
- PyAlgoEngine-0.7.4.dist-info/RECORD +43 -0
- PyAlgoEngine-0.7.4.dist-info/WHEEL +5 -0
- PyAlgoEngine-0.7.4.dist-info/top_level.txt +1 -0
- algo_engine/__init__.py +41 -0
- algo_engine/apps/__init__.py +17 -0
- algo_engine/apps/backtest/__init__.py +20 -0
- algo_engine/apps/backtest/doc_server.py +331 -0
- algo_engine/apps/backtest/tester.py +254 -0
- algo_engine/apps/backtest/web_app.py +127 -0
- algo_engine/apps/bokeh_server.py +205 -0
- algo_engine/apps/demo/__init__.py +0 -0
- algo_engine/apps/demo/test.py +39 -0
- algo_engine/backtest/__init__.py +19 -0
- algo_engine/backtest/__main__.py +51 -0
- algo_engine/backtest/metrics.py +179 -0
- algo_engine/backtest/replay.py +261 -0
- algo_engine/backtest/sim_match.py +295 -0
- algo_engine/base/__init__.py +40 -0
- algo_engine/base/console_utils.py +1070 -0
- algo_engine/base/finance_decimal.py +258 -0
- algo_engine/base/market_buffer.py +571 -0
- algo_engine/base/market_utils.py +3092 -0
- algo_engine/base/market_utils_nt.py +188 -0
- algo_engine/base/market_utils_posix.py +3004 -0
- algo_engine/base/technical_analysis.py +406 -0
- algo_engine/base/telemetrics.py +78 -0
- algo_engine/base/trade_utils.py +709 -0
- algo_engine/engine/__init__.py +28 -0
- algo_engine/engine/algo_engine.py +901 -0
- algo_engine/engine/event_engine.py +53 -0
- algo_engine/engine/market_engine.py +370 -0
- algo_engine/engine/trade_engine.py +2037 -0
- algo_engine/monitor/__init__.py +15 -0
- algo_engine/monitor/advanced_data_interface.py +239 -0
- algo_engine/profile/__init__.py +121 -0
- algo_engine/profile/cn.py +175 -0
- algo_engine/strategy/__init__.py +44 -0
- algo_engine/strategy/strategy_engine.py +440 -0
- algo_engine/utils/__init__.py +3 -0
- algo_engine/utils/commit_regularizer.py +49 -0
- algo_engine/utils/data_utils.py +251 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
__package__ = 'algo_engine.utils'
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import datetime
|
|
5
|
+
from typing import Literal, overload
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from ..profile import Profile, PROFILE
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@overload
|
|
14
|
+
def ts_indices(market_date, interval, session_start, session_end, session_break, time_zone, ts_mode: Literal['start', 'end', 'both'], ts_format='timestamp') -> list[float]:
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@overload
|
|
19
|
+
def ts_indices(market_date, interval, session_start, session_end, session_break, time_zone, ts_mode: Literal['start', 'end', 'both'], ts_format='datetime') -> list[datetime.datetime]:
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def ts_indices(
|
|
24
|
+
market_date: datetime.date = None,
|
|
25
|
+
interval: float = 60.,
|
|
26
|
+
session_start: datetime.time = datetime.time.min,
|
|
27
|
+
session_end: datetime.time = None,
|
|
28
|
+
session_break: list[tuple[datetime.time, datetime.time]] = None,
|
|
29
|
+
time_zone: datetime.tzinfo = None,
|
|
30
|
+
ts_mode: Literal['start', 'end', 'both'] | str = 'end',
|
|
31
|
+
ts_format: Literal['timestamp', 'datetime'] | str = 'timestamp'
|
|
32
|
+
) -> list[float]:
|
|
33
|
+
if market_date is None:
|
|
34
|
+
market_date = datetime.date.today()
|
|
35
|
+
|
|
36
|
+
# this is supposed to be the end_time of the given candle stick
|
|
37
|
+
market_time = datetime.datetime.combine(market_date, session_start, tzinfo=time_zone) + datetime.timedelta(seconds=interval)
|
|
38
|
+
|
|
39
|
+
if not session_end:
|
|
40
|
+
session_end = datetime.datetime.combine(market_date + datetime.timedelta(days=1), datetime.time(0), tzinfo=time_zone)
|
|
41
|
+
# session end in next day
|
|
42
|
+
elif session_end < session_start:
|
|
43
|
+
session_end = datetime.datetime.combine(market_date + datetime.timedelta(days=1), session_end, tzinfo=time_zone)
|
|
44
|
+
else:
|
|
45
|
+
session_end = datetime.datetime.combine(market_date, session_end, tzinfo=time_zone)
|
|
46
|
+
|
|
47
|
+
if ts_mode == 'both':
|
|
48
|
+
session_end += datetime.timedelta(seconds=interval)
|
|
49
|
+
|
|
50
|
+
ts_index = []
|
|
51
|
+
while market_time <= session_end:
|
|
52
|
+
# check if the given market_time is in session break
|
|
53
|
+
in_session = True
|
|
54
|
+
|
|
55
|
+
if session_break:
|
|
56
|
+
for break_range in session_break:
|
|
57
|
+
break_start, break_end = break_range
|
|
58
|
+
|
|
59
|
+
if break_start < market_time.time() <= break_end:
|
|
60
|
+
in_session = False
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
if ts_mode == 'start' or ts_mode == 'both':
|
|
64
|
+
_market_time = market_time - datetime.timedelta(seconds=interval)
|
|
65
|
+
elif ts_mode == 'end':
|
|
66
|
+
_market_time = market_time
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(f'Invalid ts_mode {ts_mode}!')
|
|
69
|
+
|
|
70
|
+
if in_session:
|
|
71
|
+
if ts_format == 'timestamp':
|
|
72
|
+
timestamp = _market_time.timestamp()
|
|
73
|
+
ts_index.append(timestamp)
|
|
74
|
+
elif ts_format == 'datetime':
|
|
75
|
+
ts_index.append(_market_time)
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError(f'Invalid ts_format {ts_format}!')
|
|
78
|
+
|
|
79
|
+
market_time += datetime.timedelta(seconds=interval)
|
|
80
|
+
|
|
81
|
+
return ts_index
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def fake_daily_data(
|
|
85
|
+
ticker: str,
|
|
86
|
+
start_date: datetime.date,
|
|
87
|
+
end_date: datetime.date,
|
|
88
|
+
p0: float = 100.,
|
|
89
|
+
volatility: float = 0.20,
|
|
90
|
+
calendar: list[datetime.date] = None,
|
|
91
|
+
**kwargs
|
|
92
|
+
) -> pd.DataFrame:
|
|
93
|
+
if calendar is None:
|
|
94
|
+
import exchange_calendars
|
|
95
|
+
market = kwargs.get('market', 'SSE')
|
|
96
|
+
calendar = exchange_calendars.get_calendar(market)
|
|
97
|
+
sessions = calendar.sessions_in_range(start_date, end_date)
|
|
98
|
+
calendar = sorted([_.date() for _ in sessions])
|
|
99
|
+
|
|
100
|
+
ttl_days = kwargs.get('ttl_days', 252)
|
|
101
|
+
risk_free_rate = kwargs.get('risk_free_rate', 0.04)
|
|
102
|
+
|
|
103
|
+
num_days = len(calendar)
|
|
104
|
+
daily_volatility = volatility / np.sqrt(ttl_days)
|
|
105
|
+
daily_risk_free_rate = np.log(1 + risk_free_rate) / ttl_days
|
|
106
|
+
|
|
107
|
+
# Generate percentage changes
|
|
108
|
+
pct_changes = np.random.lognormal(mean=daily_risk_free_rate, sigma=daily_volatility, size=num_days)
|
|
109
|
+
|
|
110
|
+
# Generate close prices
|
|
111
|
+
close_price = p0 * pct_changes.cumprod()
|
|
112
|
+
|
|
113
|
+
# Generate open, high, low prices
|
|
114
|
+
high_deviation = np.random.exponential(scale=daily_volatility, size=num_days)
|
|
115
|
+
low_deviation = -np.random.exponential(scale=daily_volatility, size=num_days)
|
|
116
|
+
|
|
117
|
+
high_price = close_price * np.exp(high_deviation)
|
|
118
|
+
low_price = close_price * np.exp(low_deviation)
|
|
119
|
+
open_price = np.random.uniform(low=low_price, high=high_price)
|
|
120
|
+
|
|
121
|
+
data = pd.DataFrame({
|
|
122
|
+
'date': calendar,
|
|
123
|
+
'open_price': open_price,
|
|
124
|
+
'high_price': high_price,
|
|
125
|
+
'low_price': low_price,
|
|
126
|
+
'close_price': close_price
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
data.set_index(keys='date', inplace=True)
|
|
130
|
+
return data
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def fake_data(
|
|
134
|
+
market_date: datetime.date,
|
|
135
|
+
p0: float = 100.,
|
|
136
|
+
volatility: float = 0.20,
|
|
137
|
+
interval: float = 60.,
|
|
138
|
+
profile: Profile = None,
|
|
139
|
+
session_start: datetime.time = None,
|
|
140
|
+
session_end: datetime.time = None,
|
|
141
|
+
session_break: list[tuple[datetime.time, datetime.time]] = None,
|
|
142
|
+
**kwargs
|
|
143
|
+
) -> pd.DataFrame:
|
|
144
|
+
if profile is None:
|
|
145
|
+
profile = PROFILE
|
|
146
|
+
|
|
147
|
+
session_start = profile.session_start if session_start is None else session_start
|
|
148
|
+
session_end = profile.session_end if session_end is None else session_end
|
|
149
|
+
session_break = profile.session_break if session_break is None else session_break
|
|
150
|
+
time_zone = kwargs.get('time_zone', profile.time_zone)
|
|
151
|
+
|
|
152
|
+
if not session_start:
|
|
153
|
+
session_start = datetime.time.min
|
|
154
|
+
|
|
155
|
+
_ts_indices = ts_indices(
|
|
156
|
+
market_date=market_date,
|
|
157
|
+
interval=interval,
|
|
158
|
+
session_start=session_start,
|
|
159
|
+
session_end=session_end,
|
|
160
|
+
session_break=session_break,
|
|
161
|
+
time_zone=time_zone,
|
|
162
|
+
ts_mode='end',
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
ttl_days = kwargs.get('ttl_days', 252)
|
|
166
|
+
risk_free_rate = kwargs.get('risk_free_rate', 0.04)
|
|
167
|
+
num_obs = len(_ts_indices)
|
|
168
|
+
obs_volatility = volatility / np.sqrt(ttl_days * num_obs)
|
|
169
|
+
obs_risk_free_rate = np.log(1 + risk_free_rate) / ttl_days / num_obs
|
|
170
|
+
|
|
171
|
+
pct_changes = np.random.lognormal(mean=obs_risk_free_rate, sigma=obs_volatility, size=num_obs)
|
|
172
|
+
close_price = p0 * pct_changes.cumprod()
|
|
173
|
+
|
|
174
|
+
# gamma distribution with shape = 1 is the same of exponential.
|
|
175
|
+
# high_deviation = np.random.gamma(shape=1, scale=obs_volatility, size=num_obs)
|
|
176
|
+
# low_deviation = -np.random.gamma(shape=1, scale=obs_volatility, size=num_obs)
|
|
177
|
+
high_deviation = np.random.exponential(scale=obs_volatility, size=num_obs)
|
|
178
|
+
low_deviation = -np.random.exponential(scale=obs_volatility, size=num_obs)
|
|
179
|
+
|
|
180
|
+
high_price = close_price * np.exp(high_deviation)
|
|
181
|
+
low_price = close_price * np.exp(low_deviation)
|
|
182
|
+
# open_price = np.random.uniform(low=low_price, high=high_price)
|
|
183
|
+
|
|
184
|
+
open_price = np.concatenate(([p0], close_price[:-1]))
|
|
185
|
+
high_price = np.max([high_price, open_price], axis=0)
|
|
186
|
+
low_price = np.min([low_price, open_price], axis=0)
|
|
187
|
+
|
|
188
|
+
data = pd.DataFrame({
|
|
189
|
+
'timestamp': _ts_indices,
|
|
190
|
+
'open_price': open_price,
|
|
191
|
+
'high_price': high_price,
|
|
192
|
+
'low_price': low_price,
|
|
193
|
+
'close_price': close_price
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
data.set_index(keys='timestamp', inplace=True)
|
|
197
|
+
return data
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def main():
|
|
201
|
+
parser = argparse.ArgumentParser(description='Generate fake market data.')
|
|
202
|
+
parser.add_argument('--ticker', type=str, default='FAKE', help='Ticker symbol for the fake data')
|
|
203
|
+
parser.add_argument('--start_date', type=str, required=True, help='Start date in YYYY-MM-DD format')
|
|
204
|
+
parser.add_argument('--end_date', type=str, required=True, help='End date in YYYY-MM-DD format')
|
|
205
|
+
parser.add_argument('--volatility', type=float, default=0.20, help='Annualized volatility of the fake data')
|
|
206
|
+
parser.add_argument('--risk_free_rate', type=float, default=0.01, help='Risk-free rate for generating fake data')
|
|
207
|
+
parser.add_argument('--seed', type=int, default=42, help='Random seed for reproducibility')
|
|
208
|
+
parser.add_argument('--minute_data', action='store_true', help='Generate minute data instead of daily data')
|
|
209
|
+
parser.add_argument('--market_date', type=str, help='Market date for minute data in YYYY-MM-DD format')
|
|
210
|
+
parser.add_argument('--p0', type=float, default=100., help='Starting price for minute data')
|
|
211
|
+
|
|
212
|
+
args = parser.parse_args()
|
|
213
|
+
|
|
214
|
+
np.random.seed(args.seed)
|
|
215
|
+
|
|
216
|
+
if args.minute_data:
|
|
217
|
+
market_date = datetime.datetime.strptime(args.market_date, '%Y-%m-%d').date()
|
|
218
|
+
data_set = fake_data(
|
|
219
|
+
market_date=market_date,
|
|
220
|
+
p0=args.p0,
|
|
221
|
+
volatility=args.volatility,
|
|
222
|
+
risk_free_rate=args.risk_free_rate
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
start_date = datetime.datetime.strptime(args.start_date, '%Y-%m-%d').date()
|
|
226
|
+
end_date = datetime.datetime.strptime(args.end_date, '%Y-%m-%d').date()
|
|
227
|
+
data_set = fake_daily_data(
|
|
228
|
+
ticker=args.ticker,
|
|
229
|
+
start_date=start_date,
|
|
230
|
+
end_date=end_date,
|
|
231
|
+
volatility=args.volatility,
|
|
232
|
+
risk_free_rate=args.risk_free_rate
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return data_set
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _test(seed: int = 42):
|
|
239
|
+
np.random.seed(seed)
|
|
240
|
+
|
|
241
|
+
# Example usage:
|
|
242
|
+
start_date = datetime.date(2024, 1, 1)
|
|
243
|
+
end_date = datetime.date(2024, 4, 1)
|
|
244
|
+
daily_data_set = fake_daily_data('FAKE', start_date, end_date)
|
|
245
|
+
minute_data_set = fake_data(market_date=start_date)
|
|
246
|
+
print(daily_data_set.head())
|
|
247
|
+
print(minute_data_set.head())
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == '__main__':
|
|
251
|
+
_test()
|