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.
Files changed (43) hide show
  1. PyAlgoEngine-0.7.4.dist-info/LICENSE +21 -0
  2. PyAlgoEngine-0.7.4.dist-info/METADATA +27 -0
  3. PyAlgoEngine-0.7.4.dist-info/RECORD +43 -0
  4. PyAlgoEngine-0.7.4.dist-info/WHEEL +5 -0
  5. PyAlgoEngine-0.7.4.dist-info/top_level.txt +1 -0
  6. algo_engine/__init__.py +41 -0
  7. algo_engine/apps/__init__.py +17 -0
  8. algo_engine/apps/backtest/__init__.py +20 -0
  9. algo_engine/apps/backtest/doc_server.py +331 -0
  10. algo_engine/apps/backtest/tester.py +254 -0
  11. algo_engine/apps/backtest/web_app.py +127 -0
  12. algo_engine/apps/bokeh_server.py +205 -0
  13. algo_engine/apps/demo/__init__.py +0 -0
  14. algo_engine/apps/demo/test.py +39 -0
  15. algo_engine/backtest/__init__.py +19 -0
  16. algo_engine/backtest/__main__.py +51 -0
  17. algo_engine/backtest/metrics.py +179 -0
  18. algo_engine/backtest/replay.py +261 -0
  19. algo_engine/backtest/sim_match.py +295 -0
  20. algo_engine/base/__init__.py +40 -0
  21. algo_engine/base/console_utils.py +1070 -0
  22. algo_engine/base/finance_decimal.py +258 -0
  23. algo_engine/base/market_buffer.py +571 -0
  24. algo_engine/base/market_utils.py +3092 -0
  25. algo_engine/base/market_utils_nt.py +188 -0
  26. algo_engine/base/market_utils_posix.py +3004 -0
  27. algo_engine/base/technical_analysis.py +406 -0
  28. algo_engine/base/telemetrics.py +78 -0
  29. algo_engine/base/trade_utils.py +709 -0
  30. algo_engine/engine/__init__.py +28 -0
  31. algo_engine/engine/algo_engine.py +901 -0
  32. algo_engine/engine/event_engine.py +53 -0
  33. algo_engine/engine/market_engine.py +370 -0
  34. algo_engine/engine/trade_engine.py +2037 -0
  35. algo_engine/monitor/__init__.py +15 -0
  36. algo_engine/monitor/advanced_data_interface.py +239 -0
  37. algo_engine/profile/__init__.py +121 -0
  38. algo_engine/profile/cn.py +175 -0
  39. algo_engine/strategy/__init__.py +44 -0
  40. algo_engine/strategy/strategy_engine.py +440 -0
  41. algo_engine/utils/__init__.py +3 -0
  42. algo_engine/utils/commit_regularizer.py +49 -0
  43. 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()