reportify-sdk 0.2.0__py3-none-any.whl → 0.2.2__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.
- reportify_sdk/quant.py +257 -196
- {reportify_sdk-0.2.0.dist-info → reportify_sdk-0.2.2.dist-info}/METADATA +1 -1
- {reportify_sdk-0.2.0.dist-info → reportify_sdk-0.2.2.dist-info}/RECORD +6 -6
- {reportify_sdk-0.2.0.dist-info → reportify_sdk-0.2.2.dist-info}/WHEEL +0 -0
- {reportify_sdk-0.2.0.dist-info → reportify_sdk-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {reportify_sdk-0.2.0.dist-info → reportify_sdk-0.2.2.dist-info}/top_level.txt +0 -0
reportify_sdk/quant.py
CHANGED
|
@@ -2,22 +2,30 @@
|
|
|
2
2
|
Quant Module
|
|
3
3
|
|
|
4
4
|
Provides quantitative analysis tools including indicators, factors, quotes, and backtesting.
|
|
5
|
+
Based on Mai-language syntax compatible with TongDaXin/TongHuaShun.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
|
-
from typing import Any
|
|
8
|
+
from typing import Any, Literal
|
|
8
9
|
|
|
9
10
|
import pandas as pd
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
StockMarket = Literal["cn", "hk", "us"]
|
|
14
|
+
|
|
15
|
+
|
|
12
16
|
class QuantModule:
|
|
13
17
|
"""
|
|
14
18
|
Quantitative analysis module
|
|
15
19
|
|
|
16
|
-
Access technical indicators, factors, quotes, and backtesting functionality.
|
|
20
|
+
Access technical indicators, factors, OHLCV quotes, and backtesting functionality.
|
|
21
|
+
Uses Mai-language syntax for formulas.
|
|
17
22
|
|
|
18
23
|
Example:
|
|
19
24
|
>>> quant = client.quant
|
|
20
|
-
>>>
|
|
25
|
+
>>> # Compute RSI indicator
|
|
26
|
+
>>> df = quant.compute_indicators(["000001"], "RSI(14)")
|
|
27
|
+
>>> # Screen stocks by formula
|
|
28
|
+
>>> stocks = quant.screen(formula="RSI(14) < 30")
|
|
21
29
|
"""
|
|
22
30
|
|
|
23
31
|
def __init__(self, client):
|
|
@@ -27,231 +35,285 @@ class QuantModule:
|
|
|
27
35
|
# Indicators
|
|
28
36
|
# -------------------------------------------------------------------------
|
|
29
37
|
|
|
30
|
-
def
|
|
38
|
+
def list_indicators(self) -> list[dict[str, Any]]:
|
|
39
|
+
"""
|
|
40
|
+
Get list of available technical indicators
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
List of indicator definitions with name, description, and fields
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> indicators = client.quant.list_indicators()
|
|
47
|
+
>>> for ind in indicators:
|
|
48
|
+
... print(f"{ind['name']}: {ind['description']}")
|
|
49
|
+
... print(f" Fields: {ind['fields']}")
|
|
50
|
+
"""
|
|
51
|
+
return self._client._get("/v1/quant/indicators")
|
|
52
|
+
|
|
53
|
+
def compute_indicators(
|
|
31
54
|
self,
|
|
32
55
|
symbols: list[str],
|
|
33
|
-
|
|
56
|
+
formula: str,
|
|
34
57
|
*,
|
|
58
|
+
market: StockMarket = "cn",
|
|
35
59
|
start_date: str | None = None,
|
|
36
60
|
end_date: str | None = None,
|
|
37
|
-
interval: str = "1d",
|
|
38
|
-
params: dict[str, Any] | None = None,
|
|
39
61
|
) -> pd.DataFrame:
|
|
40
62
|
"""
|
|
41
|
-
|
|
63
|
+
Compute indicator values for given symbols
|
|
42
64
|
|
|
43
65
|
Args:
|
|
44
|
-
symbols: List of stock
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
params: Additional indicator parameters (e.g., {"ma_period": 20})
|
|
66
|
+
symbols: List of stock codes (e.g., ["000001", "600519"])
|
|
67
|
+
formula: Indicator formula (e.g., "RSI(14)", "MACD()", "MACD(12,26,9)")
|
|
68
|
+
market: Stock market ("cn", "hk", "us"), default "cn"
|
|
69
|
+
start_date: Start date (YYYY-MM-DD), default 3 months ago
|
|
70
|
+
end_date: End date (YYYY-MM-DD), default today
|
|
50
71
|
|
|
51
72
|
Returns:
|
|
52
73
|
DataFrame with indicator values
|
|
53
74
|
|
|
54
75
|
Example:
|
|
55
|
-
>>>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
>>>
|
|
76
|
+
>>> # RSI indicator
|
|
77
|
+
>>> df = client.quant.compute_indicators(["000001"], "RSI(14)")
|
|
78
|
+
>>> print(df[["symbol", "date", "rsi"]])
|
|
79
|
+
|
|
80
|
+
>>> # MACD indicator
|
|
81
|
+
>>> df = client.quant.compute_indicators(["000001"], "MACD()")
|
|
82
|
+
>>> print(df[["symbol", "date", "dif", "dea", "macd"]])
|
|
83
|
+
|
|
84
|
+
>>> # KDJ indicator
|
|
85
|
+
>>> df = client.quant.compute_indicators(["000001"], "KDJ(9,3,3)")
|
|
86
|
+
>>> print(df[["symbol", "date", "k", "d", "j"]])
|
|
61
87
|
"""
|
|
62
88
|
data: dict[str, Any] = {
|
|
63
89
|
"symbols": symbols,
|
|
64
|
-
"
|
|
65
|
-
"
|
|
90
|
+
"formula": formula,
|
|
91
|
+
"market": market,
|
|
66
92
|
}
|
|
67
93
|
if start_date:
|
|
68
94
|
data["start_date"] = start_date
|
|
69
95
|
if end_date:
|
|
70
96
|
data["end_date"] = end_date
|
|
71
|
-
if params:
|
|
72
|
-
data["params"] = params
|
|
73
97
|
|
|
74
|
-
response = self._client._post("/
|
|
75
|
-
return self._to_dataframe(response.get("
|
|
98
|
+
response = self._client._post("/v1/quant/indicators/compute", json=data)
|
|
99
|
+
return self._to_dataframe(response.get("datas", []))
|
|
100
|
+
|
|
101
|
+
# -------------------------------------------------------------------------
|
|
102
|
+
# Factors
|
|
103
|
+
# -------------------------------------------------------------------------
|
|
76
104
|
|
|
77
|
-
def
|
|
105
|
+
def list_factors(self) -> list[dict[str, Any]]:
|
|
78
106
|
"""
|
|
79
|
-
Get list of available
|
|
107
|
+
Get list of available factors (variables and functions)
|
|
108
|
+
|
|
109
|
+
Returns factors organized by level:
|
|
110
|
+
- Level 0 Variables: CLOSE, OPEN, HIGH, LOW, VOLUME
|
|
111
|
+
- Level 0 Functions: MA, EMA, REF, HHV, LLV, STD, etc.
|
|
112
|
+
- Level 1 Functions: CROSS, COUNT, EVERY, etc.
|
|
113
|
+
- Level 2 Functions: MACD, KDJ, RSI, BOLL, etc.
|
|
80
114
|
|
|
81
115
|
Returns:
|
|
82
|
-
List of
|
|
116
|
+
List of factor definitions
|
|
83
117
|
|
|
84
118
|
Example:
|
|
85
|
-
>>>
|
|
86
|
-
>>> for
|
|
87
|
-
... print(f"{
|
|
119
|
+
>>> factors = client.quant.list_factors()
|
|
120
|
+
>>> for f in factors:
|
|
121
|
+
... print(f"{f['name']} ({f['type']}, level {f['level']})")
|
|
88
122
|
"""
|
|
89
|
-
|
|
90
|
-
return response.get("indicators", [])
|
|
91
|
-
|
|
92
|
-
# -------------------------------------------------------------------------
|
|
93
|
-
# Factors
|
|
94
|
-
# -------------------------------------------------------------------------
|
|
123
|
+
return self._client._get("/v1/quant/factors")
|
|
95
124
|
|
|
96
|
-
def
|
|
125
|
+
def compute_factors(
|
|
97
126
|
self,
|
|
98
127
|
symbols: list[str],
|
|
99
|
-
|
|
128
|
+
formula: str,
|
|
100
129
|
*,
|
|
130
|
+
market: StockMarket = "cn",
|
|
101
131
|
start_date: str | None = None,
|
|
102
132
|
end_date: str | None = None,
|
|
103
|
-
frequency: str = "daily",
|
|
104
133
|
) -> pd.DataFrame:
|
|
105
134
|
"""
|
|
106
|
-
|
|
135
|
+
Compute factor values for given symbols
|
|
136
|
+
|
|
137
|
+
Uses Mai-language syntax compatible with TongDaXin/TongHuaShun.
|
|
107
138
|
|
|
108
139
|
Args:
|
|
109
|
-
symbols: List of stock
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
140
|
+
symbols: List of stock codes
|
|
141
|
+
formula: Factor formula using Mai-language syntax
|
|
142
|
+
market: Stock market ("cn", "hk", "us"), default "cn"
|
|
143
|
+
start_date: Start date (YYYY-MM-DD), default 3 months ago
|
|
144
|
+
end_date: End date (YYYY-MM-DD), default today
|
|
114
145
|
|
|
115
146
|
Returns:
|
|
116
147
|
DataFrame with factor values
|
|
117
148
|
|
|
118
149
|
Example:
|
|
119
|
-
>>>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
>>> # Simple indicator
|
|
151
|
+
>>> df = client.quant.compute_factors(["000001"], "RSI(14)")
|
|
152
|
+
|
|
153
|
+
>>> # MACD DIF line
|
|
154
|
+
>>> df = client.quant.compute_factors(["000001"], "MACD().dif")
|
|
155
|
+
|
|
156
|
+
>>> # Close above 20-day MA (boolean)
|
|
157
|
+
>>> df = client.quant.compute_factors(["000001"], "CLOSE > MA(20)")
|
|
158
|
+
|
|
159
|
+
>>> # Deviation from MA20 in percent
|
|
160
|
+
>>> df = client.quant.compute_factors(["000001"], "(CLOSE - MA(20)) / MA(20) * 100")
|
|
161
|
+
|
|
162
|
+
Supported Operators:
|
|
163
|
+
- Comparison: >, <, >=, <=, ==, !=
|
|
164
|
+
- Logical AND: & (NOT "AND")
|
|
165
|
+
- Logical OR: | (NOT "OR")
|
|
166
|
+
- Arithmetic: +, -, *, /
|
|
167
|
+
|
|
168
|
+
Supported Variables:
|
|
169
|
+
- CLOSE, C: Close price
|
|
170
|
+
- OPEN, O: Open price
|
|
171
|
+
- HIGH, H: High price
|
|
172
|
+
- LOW, L: Low price
|
|
173
|
+
- VOLUME, V, VOL: Volume
|
|
124
174
|
"""
|
|
125
175
|
data: dict[str, Any] = {
|
|
126
176
|
"symbols": symbols,
|
|
127
|
-
"
|
|
128
|
-
"
|
|
177
|
+
"formula": formula,
|
|
178
|
+
"market": market,
|
|
129
179
|
}
|
|
130
180
|
if start_date:
|
|
131
181
|
data["start_date"] = start_date
|
|
132
182
|
if end_date:
|
|
133
183
|
data["end_date"] = end_date
|
|
134
184
|
|
|
135
|
-
response = self._client._post("/
|
|
136
|
-
return self._to_dataframe(response.get("
|
|
137
|
-
|
|
138
|
-
def factor_list(self) -> list[dict[str, Any]]:
|
|
139
|
-
"""
|
|
140
|
-
Get list of available factors
|
|
185
|
+
response = self._client._post("/v1/quant/factors/compute", json=data)
|
|
186
|
+
return self._to_dataframe(response.get("datas", []))
|
|
141
187
|
|
|
142
|
-
|
|
143
|
-
List of factor definitions
|
|
144
|
-
|
|
145
|
-
Example:
|
|
146
|
-
>>> factors = client.quant.factor_list()
|
|
147
|
-
>>> for f in factors:
|
|
148
|
-
... print(f"{f['name']}: {f['category']}")
|
|
149
|
-
"""
|
|
150
|
-
response = self._client._get("/v2/quant/factors")
|
|
151
|
-
return response.get("factors", [])
|
|
152
|
-
|
|
153
|
-
def factor_exposure(
|
|
188
|
+
def screen(
|
|
154
189
|
self,
|
|
155
|
-
|
|
190
|
+
formula: str,
|
|
156
191
|
*,
|
|
157
|
-
|
|
192
|
+
market: StockMarket = "cn",
|
|
193
|
+
check_date: str | None = None,
|
|
194
|
+
symbols: list[str] | None = None,
|
|
158
195
|
) -> pd.DataFrame:
|
|
159
196
|
"""
|
|
160
|
-
|
|
197
|
+
Screen stocks based on factor formula
|
|
198
|
+
|
|
199
|
+
Returns stocks where the formula evaluates to True (for boolean formulas)
|
|
200
|
+
or non-null (for numeric formulas).
|
|
161
201
|
|
|
162
202
|
Args:
|
|
163
|
-
|
|
164
|
-
|
|
203
|
+
formula: Screening formula using Mai-language syntax
|
|
204
|
+
market: Stock market ("cn", "hk", "us"), default "cn"
|
|
205
|
+
check_date: Check date (YYYY-MM-DD), default latest trading day
|
|
206
|
+
symbols: Stock codes to screen (None = full market)
|
|
165
207
|
|
|
166
208
|
Returns:
|
|
167
|
-
DataFrame with
|
|
209
|
+
DataFrame with stocks that passed the filter
|
|
168
210
|
|
|
169
211
|
Example:
|
|
170
|
-
>>>
|
|
171
|
-
>>>
|
|
212
|
+
>>> # RSI oversold
|
|
213
|
+
>>> stocks = client.quant.screen(formula="RSI(14) < 30")
|
|
214
|
+
|
|
215
|
+
>>> # Golden cross
|
|
216
|
+
>>> stocks = client.quant.screen(formula="CROSS(MA(5), MA(10))")
|
|
217
|
+
|
|
218
|
+
>>> # Uptrend
|
|
219
|
+
>>> stocks = client.quant.screen(formula="(CLOSE > MA(20)) & (MA(20) > MA(60))")
|
|
220
|
+
|
|
221
|
+
>>> # Above upper Bollinger Band
|
|
222
|
+
>>> stocks = client.quant.screen(formula="CLOSE > BOLL(20, 2).upper")
|
|
223
|
+
|
|
224
|
+
>>> # Screen specific stocks
|
|
225
|
+
>>> stocks = client.quant.screen(
|
|
226
|
+
... formula="RSI(14) < 30",
|
|
227
|
+
... symbols=["000001", "600519", "000002"]
|
|
228
|
+
... )
|
|
172
229
|
"""
|
|
173
|
-
data: dict[str, Any] = {
|
|
174
|
-
|
|
175
|
-
|
|
230
|
+
data: dict[str, Any] = {
|
|
231
|
+
"formula": formula,
|
|
232
|
+
"market": market,
|
|
233
|
+
}
|
|
234
|
+
if check_date:
|
|
235
|
+
data["check_date"] = check_date
|
|
236
|
+
if symbols:
|
|
237
|
+
data["symbols"] = symbols
|
|
176
238
|
|
|
177
|
-
response = self._client._post("/
|
|
178
|
-
return self._to_dataframe(response.get("
|
|
239
|
+
response = self._client._post("/v1/quant/factors/screen", json=data)
|
|
240
|
+
return self._to_dataframe(response.get("datas", []))
|
|
179
241
|
|
|
180
242
|
# -------------------------------------------------------------------------
|
|
181
243
|
# Quotes
|
|
182
244
|
# -------------------------------------------------------------------------
|
|
183
245
|
|
|
184
|
-
def
|
|
246
|
+
def ohlcv(
|
|
185
247
|
self,
|
|
186
|
-
|
|
248
|
+
symbol: str,
|
|
187
249
|
*,
|
|
188
|
-
|
|
250
|
+
market: StockMarket = "cn",
|
|
251
|
+
start_date: str | None = None,
|
|
252
|
+
end_date: str | None = None,
|
|
189
253
|
) -> pd.DataFrame:
|
|
190
254
|
"""
|
|
191
|
-
Get
|
|
255
|
+
Get OHLCV (Open, High, Low, Close, Volume) daily data for a single symbol
|
|
192
256
|
|
|
193
257
|
Args:
|
|
194
|
-
|
|
195
|
-
|
|
258
|
+
symbol: Stock code
|
|
259
|
+
market: Stock market ("cn", "hk", "us"), default "cn"
|
|
260
|
+
start_date: Start date (YYYY-MM-DD), default 1 month ago
|
|
261
|
+
end_date: End date (YYYY-MM-DD), default today
|
|
196
262
|
|
|
197
263
|
Returns:
|
|
198
|
-
DataFrame with
|
|
264
|
+
DataFrame with OHLCV data
|
|
199
265
|
|
|
200
266
|
Example:
|
|
201
|
-
>>>
|
|
202
|
-
>>> print(
|
|
267
|
+
>>> df = client.quant.ohlcv("000001")
|
|
268
|
+
>>> print(df[["open", "high", "low", "close", "volume"]])
|
|
203
269
|
"""
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
270
|
+
params: dict[str, Any] = {
|
|
271
|
+
"symbol": symbol,
|
|
272
|
+
"market": market,
|
|
273
|
+
}
|
|
274
|
+
if start_date:
|
|
275
|
+
params["start_date"] = start_date
|
|
276
|
+
if end_date:
|
|
277
|
+
params["end_date"] = end_date
|
|
207
278
|
|
|
208
|
-
response = self._client.
|
|
209
|
-
return self._to_dataframe(response.get("
|
|
279
|
+
response = self._client._get("/v1/quant/quotes/ohlcv", params=params)
|
|
280
|
+
return self._to_dataframe(response.get("datas", []))
|
|
210
281
|
|
|
211
|
-
def
|
|
282
|
+
def ohlcv_batch(
|
|
212
283
|
self,
|
|
213
284
|
symbols: list[str],
|
|
214
285
|
*,
|
|
286
|
+
market: StockMarket = "cn",
|
|
215
287
|
start_date: str | None = None,
|
|
216
288
|
end_date: str | None = None,
|
|
217
|
-
interval: str = "1d",
|
|
218
|
-
adjust: str = "forward",
|
|
219
|
-
limit: int = 100,
|
|
220
289
|
) -> pd.DataFrame:
|
|
221
290
|
"""
|
|
222
|
-
Get
|
|
291
|
+
Get OHLCV data for multiple symbols
|
|
223
292
|
|
|
224
293
|
Args:
|
|
225
|
-
symbols: List of stock
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
adjust: Adjustment type ("forward", "backward", "none")
|
|
230
|
-
limit: Maximum records per symbol
|
|
294
|
+
symbols: List of stock codes
|
|
295
|
+
market: Stock market ("cn", "hk", "us"), default "cn"
|
|
296
|
+
start_date: Start date (YYYY-MM-DD), default 1 month ago
|
|
297
|
+
end_date: End date (YYYY-MM-DD), default today
|
|
231
298
|
|
|
232
299
|
Returns:
|
|
233
|
-
DataFrame with
|
|
300
|
+
DataFrame with OHLCV data sorted by date (descending), then by symbol
|
|
234
301
|
|
|
235
302
|
Example:
|
|
236
|
-
>>>
|
|
237
|
-
|
|
238
|
-
... start_date="2024-01-01",
|
|
239
|
-
... interval="1d"
|
|
240
|
-
... )
|
|
303
|
+
>>> df = client.quant.ohlcv_batch(["000001", "600519"])
|
|
304
|
+
>>> print(df[["symbol", "date", "close", "volume"]])
|
|
241
305
|
"""
|
|
242
306
|
data: dict[str, Any] = {
|
|
243
307
|
"symbols": symbols,
|
|
244
|
-
"
|
|
245
|
-
"adjust": adjust,
|
|
246
|
-
"limit": limit,
|
|
308
|
+
"market": market,
|
|
247
309
|
}
|
|
248
310
|
if start_date:
|
|
249
311
|
data["start_date"] = start_date
|
|
250
312
|
if end_date:
|
|
251
313
|
data["end_date"] = end_date
|
|
252
314
|
|
|
253
|
-
response = self._client._post("/
|
|
254
|
-
return self._to_dataframe(response.get("
|
|
315
|
+
response = self._client._post("/v1/quant/quotes/ohlcv/batch", json=data)
|
|
316
|
+
return self._to_dataframe(response.get("datas", []))
|
|
255
317
|
|
|
256
318
|
# -------------------------------------------------------------------------
|
|
257
319
|
# Backtest
|
|
@@ -259,99 +321,100 @@ class QuantModule:
|
|
|
259
321
|
|
|
260
322
|
def backtest(
|
|
261
323
|
self,
|
|
262
|
-
strategy: dict[str, Any],
|
|
263
|
-
*,
|
|
264
324
|
start_date: str,
|
|
265
325
|
end_date: str,
|
|
266
|
-
|
|
267
|
-
|
|
326
|
+
symbol: str,
|
|
327
|
+
entry_formula: str,
|
|
328
|
+
*,
|
|
329
|
+
exit_formula: str | None = None,
|
|
330
|
+
market: StockMarket = "cn",
|
|
331
|
+
initial_cash: float = 100000.0,
|
|
332
|
+
commission: float = 0.0,
|
|
333
|
+
stop_loss: float = 0.0,
|
|
334
|
+
sizer_percent: int = 99,
|
|
335
|
+
auto_close: bool = True,
|
|
336
|
+
labels: dict[str, str] | None = None,
|
|
268
337
|
) -> dict[str, Any]:
|
|
269
338
|
"""
|
|
270
|
-
|
|
339
|
+
Execute strategy backtest
|
|
271
340
|
|
|
272
341
|
Args:
|
|
273
|
-
strategy: Strategy definition dict
|
|
274
342
|
start_date: Backtest start date (YYYY-MM-DD)
|
|
275
343
|
end_date: Backtest end date (YYYY-MM-DD)
|
|
276
|
-
|
|
277
|
-
|
|
344
|
+
symbol: Stock code
|
|
345
|
+
market: Stock market ("cn", "hk", "us"), default "cn"
|
|
346
|
+
entry_formula: Buy/long entry formula (result > 0 triggers buy)
|
|
347
|
+
exit_formula: Sell/close formula (result > 0 triggers sell), optional
|
|
348
|
+
initial_cash: Initial capital (default: 100000.0)
|
|
349
|
+
commission: Commission rate (default: 0.0)
|
|
350
|
+
stop_loss: Stop loss setting (default: 0.0, no stop loss)
|
|
351
|
+
sizer_percent: Position percentage (default: 99%)
|
|
352
|
+
auto_close: Auto close positions (default: True)
|
|
353
|
+
labels: Label dict for returning extra indicator values, e.g. {"up": "CROSS(MA(20), MA(60))"}
|
|
278
354
|
|
|
279
355
|
Returns:
|
|
280
|
-
Backtest results including
|
|
356
|
+
Backtest results including:
|
|
357
|
+
- success: Whether backtest succeeded
|
|
358
|
+
- initial_cash: Initial capital
|
|
359
|
+
- final_cash: Final capital
|
|
360
|
+
- total_return: Total return
|
|
361
|
+
- total_return_pct: Total return percentage
|
|
362
|
+
- max_drawdown: Maximum drawdown
|
|
363
|
+
- profit_factor: Profit factor
|
|
364
|
+
- win_rate: Win rate
|
|
365
|
+
- total_trades: Total number of trades
|
|
366
|
+
- trades: List of trade details
|
|
281
367
|
|
|
282
368
|
Example:
|
|
369
|
+
>>> # Simple golden cross strategy
|
|
283
370
|
>>> result = client.quant.backtest(
|
|
284
|
-
...
|
|
285
|
-
... "type": "momentum",
|
|
286
|
-
... "universe": ["US:AAPL", "US:MSFT", "US:GOOGL"],
|
|
287
|
-
... "rebalance": "monthly",
|
|
288
|
-
... "top_n": 2
|
|
289
|
-
... },
|
|
290
|
-
... start_date="2020-01-01",
|
|
371
|
+
... start_date="2023-01-01",
|
|
291
372
|
... end_date="2024-01-01",
|
|
292
|
-
...
|
|
373
|
+
... symbol="000001",
|
|
374
|
+
... entry_formula="CROSS(MA(5), MA(20))", # Buy when MA5 crosses above MA20
|
|
375
|
+
... initial_cash=100000
|
|
376
|
+
... )
|
|
377
|
+
>>> print(f"Total Return: {result['total_return_pct']:.2%}")
|
|
378
|
+
>>> print(f"Max Drawdown: {result['max_drawdown']:.2%}")
|
|
379
|
+
>>> print(f"Win Rate: {result['win_rate']:.2%}")
|
|
380
|
+
|
|
381
|
+
>>> # Strategy with entry and exit signals
|
|
382
|
+
>>> result = client.quant.backtest(
|
|
383
|
+
... start_date="2023-01-01",
|
|
384
|
+
... end_date="2024-01-01",
|
|
385
|
+
... symbol="000001",
|
|
386
|
+
... entry_formula="CROSS(MA(5), MA(20))", # Buy signal
|
|
387
|
+
... exit_formula="CROSSDOWN(MA(5), MA(20))" # Sell signal
|
|
388
|
+
... )
|
|
389
|
+
|
|
390
|
+
>>> # With custom labels for analysis
|
|
391
|
+
>>> result = client.quant.backtest(
|
|
392
|
+
... start_date="2023-01-01",
|
|
393
|
+
... end_date="2024-01-01",
|
|
394
|
+
... symbol="000001",
|
|
395
|
+
... entry_formula="RSI(14) < 30",
|
|
396
|
+
... exit_formula="RSI(14) > 70",
|
|
397
|
+
... labels={"rsi": "RSI(14)", "ma20": "MA(20)"}
|
|
293
398
|
... )
|
|
294
|
-
>>> print(f"Total Return: {result['metrics']['total_return']:.2%}")
|
|
295
399
|
"""
|
|
296
400
|
data = {
|
|
297
|
-
"strategy": strategy,
|
|
298
401
|
"start_date": start_date,
|
|
299
402
|
"end_date": end_date,
|
|
300
|
-
"
|
|
403
|
+
"symbol": symbol,
|
|
404
|
+
"market": market,
|
|
405
|
+
"entry_formula": entry_formula,
|
|
406
|
+
"initial_cash": initial_cash,
|
|
407
|
+
"commission": commission,
|
|
408
|
+
"stop_loss": stop_loss,
|
|
409
|
+
"sizer_percent": sizer_percent,
|
|
410
|
+
"auto_close": auto_close,
|
|
301
411
|
}
|
|
302
|
-
if
|
|
303
|
-
data["
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def backtest_result(self, backtest_id: str) -> dict[str, Any]:
|
|
308
|
-
"""
|
|
309
|
-
Get backtest result by ID
|
|
310
|
-
|
|
311
|
-
Args:
|
|
312
|
-
backtest_id: Backtest job ID
|
|
313
|
-
|
|
314
|
-
Returns:
|
|
315
|
-
Backtest results
|
|
316
|
-
|
|
317
|
-
Example:
|
|
318
|
-
>>> result = client.quant.backtest_result("bt_abc123")
|
|
319
|
-
"""
|
|
320
|
-
return self._client._get(f"/v2/quant/backtest/{backtest_id}")
|
|
321
|
-
|
|
322
|
-
def backtest_returns(self, backtest_id: str) -> pd.DataFrame:
|
|
323
|
-
"""
|
|
324
|
-
Get backtest return series
|
|
412
|
+
if exit_formula is not None:
|
|
413
|
+
data["exit_formula"] = exit_formula
|
|
414
|
+
if labels is not None:
|
|
415
|
+
data["labels"] = labels
|
|
325
416
|
|
|
326
|
-
|
|
327
|
-
backtest_id: Backtest job ID
|
|
328
|
-
|
|
329
|
-
Returns:
|
|
330
|
-
DataFrame with daily returns
|
|
331
|
-
|
|
332
|
-
Example:
|
|
333
|
-
>>> returns = client.quant.backtest_returns("bt_abc123")
|
|
334
|
-
>>> print(returns[["date", "portfolio_value", "daily_return"]])
|
|
335
|
-
"""
|
|
336
|
-
response = self._client._get(f"/v2/quant/backtest/{backtest_id}/returns")
|
|
337
|
-
return self._to_dataframe(response.get("data", []))
|
|
338
|
-
|
|
339
|
-
def backtest_trades(self, backtest_id: str) -> pd.DataFrame:
|
|
340
|
-
"""
|
|
341
|
-
Get backtest trade history
|
|
342
|
-
|
|
343
|
-
Args:
|
|
344
|
-
backtest_id: Backtest job ID
|
|
345
|
-
|
|
346
|
-
Returns:
|
|
347
|
-
DataFrame with all trades
|
|
348
|
-
|
|
349
|
-
Example:
|
|
350
|
-
>>> trades = client.quant.backtest_trades("bt_abc123")
|
|
351
|
-
>>> print(trades[["date", "symbol", "action", "quantity", "price"]])
|
|
352
|
-
"""
|
|
353
|
-
response = self._client._get(f"/v2/quant/backtest/{backtest_id}/trades")
|
|
354
|
-
return self._to_dataframe(response.get("data", []))
|
|
417
|
+
return self._client._post("/v1/quant/backtest", json=data)
|
|
355
418
|
|
|
356
419
|
# -------------------------------------------------------------------------
|
|
357
420
|
# Helper Methods
|
|
@@ -363,9 +426,7 @@ class QuantModule:
|
|
|
363
426
|
return pd.DataFrame()
|
|
364
427
|
df = pd.DataFrame(data)
|
|
365
428
|
# Convert date columns if present
|
|
366
|
-
for col in ["date", "
|
|
429
|
+
for col in ["date", "check_date", "trade_date"]:
|
|
367
430
|
if col in df.columns:
|
|
368
431
|
df[col] = pd.to_datetime(df[col])
|
|
369
|
-
df = df.set_index(col)
|
|
370
|
-
break
|
|
371
432
|
return df
|
|
@@ -3,11 +3,11 @@ reportify_sdk/client.py,sha256=P56qBme48UPkZhMb9BlDnLMLXwHK-DjU7qDhGFFxMI0,11019
|
|
|
3
3
|
reportify_sdk/docs.py,sha256=O30I1J4qTC8zpkVP_qu1cgT8nv6ysXNClqvaiRb0lhY,4957
|
|
4
4
|
reportify_sdk/exceptions.py,sha256=r2_C_kTh6tCrQnfA3UozSqMMA-2OBnoP3pGpgYeqcdU,1049
|
|
5
5
|
reportify_sdk/kb.py,sha256=-4UHWtudFncGRBB42B4YEoMPsEHr4QOtY55_8hKyogE,2240
|
|
6
|
-
reportify_sdk/quant.py,sha256=
|
|
6
|
+
reportify_sdk/quant.py,sha256=9rWKWo2UlG3GfsQC-jpEgXUl25ojEabpvOGISyGMamM,15110
|
|
7
7
|
reportify_sdk/stock.py,sha256=NcEhV9JfIQm2BsfYM1k-JVcxHhnnil32dp-bedGG4BU,13513
|
|
8
8
|
reportify_sdk/timeline.py,sha256=b5Zj5SjXT4gP0dvEl8gXCWDZJHemyU7NSYahet2nHhc,4075
|
|
9
|
-
reportify_sdk-0.2.
|
|
10
|
-
reportify_sdk-0.2.
|
|
11
|
-
reportify_sdk-0.2.
|
|
12
|
-
reportify_sdk-0.2.
|
|
13
|
-
reportify_sdk-0.2.
|
|
9
|
+
reportify_sdk-0.2.2.dist-info/licenses/LICENSE,sha256=zBUq4DL4lE-fZU_PMkr0gnxkYS1LhdRHFw8_LmCb-ek,1066
|
|
10
|
+
reportify_sdk-0.2.2.dist-info/METADATA,sha256=mKL0oZbmcTLi0pGVLPZDYiULHI5g_qZ_-PDjnNOinz0,4311
|
|
11
|
+
reportify_sdk-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
reportify_sdk-0.2.2.dist-info/top_level.txt,sha256=tc_dzCSWIDsNbHSi-FlyEEX8xwinhN9gl-CwyLRE4B0,14
|
|
13
|
+
reportify_sdk-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|