jcback 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.
- jcback/__init__.py +89 -0
- jcback/config.yaml +56 -0
- jcback/entry.py +355 -0
- jcback/product_fees.yaml +494 -0
- jcback/runtime/__init__.py +58 -0
- jcback/runtime/config.py +169 -0
- jcback/runtime/constants.py +12 -0
- jcback/runtime/context.py +138 -0
- jcback/runtime/data.py +215 -0
- jcback/runtime/driver.py +192 -0
- jcback/runtime/events.py +46 -0
- jcback/runtime/export.py +239 -0
- jcback/runtime/fees.py +305 -0
- jcback/runtime/ledger.py +558 -0
- jcback/runtime/multi_runner.py +328 -0
- jcback/runtime/portfolio.py +378 -0
- jcback/runtime/report.py +83 -0
- jcback/runtime/result.py +130 -0
- jcback/runtime/strategy.py +141 -0
- jcback/sim/__init__.py +7 -0
- jcback/sim/algo.py +162 -0
- jcback/sim/facade.py +13 -0
- jcback/sim/loop.py +213 -0
- jcback/sim/match.py +285 -0
- jcback/sim/metrics.py +253 -0
- jcback/sim/series.py +337 -0
- jcback/sim/types.py +279 -0
- jcback-0.1.1.dist-info/METADATA +168 -0
- jcback-0.1.1.dist-info/RECORD +31 -0
- jcback-0.1.1.dist-info/WHEEL +5 -0
- jcback-0.1.1.dist-info/top_level.txt +1 -0
jcback/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
jcback — 事件驱动 A 股回测框架。
|
|
3
|
+
|
|
4
|
+
函数式策略(推荐,与几策平台 init / on_bar 写法一致)::
|
|
5
|
+
|
|
6
|
+
def init(context): ...
|
|
7
|
+
def on_bar(context, bars): ...
|
|
8
|
+
|
|
9
|
+
def run_main():
|
|
10
|
+
run(
|
|
11
|
+
strategy_id="demo",
|
|
12
|
+
mode=MODE_BACKTEST,
|
|
13
|
+
symbols="600000.SH",
|
|
14
|
+
backtest_start_time="2025-01-02",
|
|
15
|
+
backtest_end_time="2025-03-31",
|
|
16
|
+
backtest_match_mode=MATCH_NEXT_OPEN,
|
|
17
|
+
init_func=init,
|
|
18
|
+
on_bar=on_bar,
|
|
19
|
+
use_mock=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
类式策略(jcback.Strategy 子类)::
|
|
23
|
+
|
|
24
|
+
from jcback import run_backtest, Strategy
|
|
25
|
+
result = run_backtest(MyStrategy, use_mock=True, symbols="600000.SH", start_date="2025-01-02", ...)
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from .sim.facade import Backtest, Strategy
|
|
30
|
+
from .runtime import (
|
|
31
|
+
ADJUST_NONE,
|
|
32
|
+
ADJUST_POST,
|
|
33
|
+
ADJUST_PREV,
|
|
34
|
+
Bar,
|
|
35
|
+
BacktestConfig,
|
|
36
|
+
BacktestEvent,
|
|
37
|
+
BacktestResult,
|
|
38
|
+
Context,
|
|
39
|
+
EventBacktest,
|
|
40
|
+
EventBus,
|
|
41
|
+
FunctionStrategy,
|
|
42
|
+
MATCH_CURRENT_CLOSE,
|
|
43
|
+
MATCH_NEXT_OPEN,
|
|
44
|
+
MODE_BACKTEST,
|
|
45
|
+
infer_asset_type,
|
|
46
|
+
jcdata_to_ohlcv,
|
|
47
|
+
load_from_jcdata,
|
|
48
|
+
load_multi_ohlcv,
|
|
49
|
+
make_mock_jcdata,
|
|
50
|
+
parse_symbols,
|
|
51
|
+
resolve_config,
|
|
52
|
+
)
|
|
53
|
+
from .runtime.export import emit_report_marker, get_last_result, serialize_for_ui
|
|
54
|
+
from .runtime.report import print_backtest_report, setup_logger
|
|
55
|
+
from .entry import default_backtest_window, run, run_backtest
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"ADJUST_NONE",
|
|
59
|
+
"ADJUST_POST",
|
|
60
|
+
"ADJUST_PREV",
|
|
61
|
+
"Backtest",
|
|
62
|
+
"BacktestConfig",
|
|
63
|
+
"BacktestEvent",
|
|
64
|
+
"BacktestResult",
|
|
65
|
+
"Bar",
|
|
66
|
+
"Context",
|
|
67
|
+
"EventBacktest",
|
|
68
|
+
"EventBus",
|
|
69
|
+
"FunctionStrategy",
|
|
70
|
+
"MATCH_CURRENT_CLOSE",
|
|
71
|
+
"MATCH_NEXT_OPEN",
|
|
72
|
+
"MODE_BACKTEST",
|
|
73
|
+
"Strategy",
|
|
74
|
+
"default_backtest_window",
|
|
75
|
+
"infer_asset_type",
|
|
76
|
+
"jcdata_to_ohlcv",
|
|
77
|
+
"load_from_jcdata",
|
|
78
|
+
"load_multi_ohlcv",
|
|
79
|
+
"make_mock_jcdata",
|
|
80
|
+
"parse_symbols",
|
|
81
|
+
"emit_report_marker",
|
|
82
|
+
"get_last_result",
|
|
83
|
+
"print_backtest_report",
|
|
84
|
+
"resolve_config",
|
|
85
|
+
"run",
|
|
86
|
+
"run_backtest",
|
|
87
|
+
"serialize_for_ui",
|
|
88
|
+
"setup_logger",
|
|
89
|
+
]
|
jcback/config.yaml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# jcback 回测默认配置
|
|
2
|
+
# 手续费规则:按品种区分买卖、期货平昨/平今;支持固定费 + 成交额比例
|
|
3
|
+
|
|
4
|
+
commission:
|
|
5
|
+
# 未命中品种规则时的兜底
|
|
6
|
+
default:
|
|
7
|
+
buy_ratio: 0.0001
|
|
8
|
+
sell_ratio: 0.0001
|
|
9
|
+
fixed: 0.0
|
|
10
|
+
min_commission: 0.0
|
|
11
|
+
stamp_tax_ratio: 0.0
|
|
12
|
+
|
|
13
|
+
by_asset:
|
|
14
|
+
# A 股:买卖佣金不同,卖出另收印花税
|
|
15
|
+
stock:
|
|
16
|
+
buy_ratio: 0.00025
|
|
17
|
+
sell_ratio: 0.00025
|
|
18
|
+
fixed: 0.0
|
|
19
|
+
min_commission: 5.0
|
|
20
|
+
stamp_tax_ratio: 0.001
|
|
21
|
+
|
|
22
|
+
etf:
|
|
23
|
+
buy_ratio: 0.00025
|
|
24
|
+
sell_ratio: 0.00025
|
|
25
|
+
fixed: 0.0
|
|
26
|
+
min_commission: 5.0
|
|
27
|
+
stamp_tax_ratio: 0.0
|
|
28
|
+
|
|
29
|
+
# 可转债:买卖费率通常一致,无印花税
|
|
30
|
+
convertible_bond:
|
|
31
|
+
buy_ratio: 0.00005
|
|
32
|
+
sell_ratio: 0.00005
|
|
33
|
+
fixed: 0.0
|
|
34
|
+
min_commission: 0.0
|
|
35
|
+
stamp_tax_ratio: 0.0
|
|
36
|
+
|
|
37
|
+
bond:
|
|
38
|
+
buy_ratio: 0.00005
|
|
39
|
+
sell_ratio: 0.00005
|
|
40
|
+
fixed: 0.0
|
|
41
|
+
min_commission: 0.0
|
|
42
|
+
stamp_tax_ratio: 0.0
|
|
43
|
+
|
|
44
|
+
# 期货:通用兜底(未在 product_fees.yaml 列出的品种使用)
|
|
45
|
+
future:
|
|
46
|
+
multiplier: 1.0
|
|
47
|
+
open_ratio: 0.000023
|
|
48
|
+
open_fixed: 0.0
|
|
49
|
+
close_yesterday_ratio: 0.000023
|
|
50
|
+
close_yesterday_fixed: 0.0
|
|
51
|
+
close_today_ratio: 0.00023
|
|
52
|
+
close_today_fixed: 0.0
|
|
53
|
+
min_commission: 0.0
|
|
54
|
+
|
|
55
|
+
# 中国期货各品种交易所标准费率表(按品种代码 IF/RB/MA 等匹配)
|
|
56
|
+
future_fee_table: product_fees.yaml
|
jcback/entry.py
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""
|
|
2
|
+
jcback 主入口:统一 run() 风格(与几策策略脚本一致)。
|
|
3
|
+
|
|
4
|
+
策略文件定义 init / on_bar / on_backtest_finished,在 run_main() 中调用 run()。
|
|
5
|
+
支持单标的与多标的组合回测。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Callable
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from loguru import logger
|
|
14
|
+
|
|
15
|
+
from .sim.facade import Strategy
|
|
16
|
+
from .runtime.constants import (
|
|
17
|
+
ADJUST_NONE,
|
|
18
|
+
ADJUST_POST,
|
|
19
|
+
ADJUST_PREV,
|
|
20
|
+
MATCH_CURRENT_CLOSE,
|
|
21
|
+
MATCH_NEXT_OPEN,
|
|
22
|
+
MODE_BACKTEST,
|
|
23
|
+
)
|
|
24
|
+
from .runtime.data import jcdata_to_ohlcv, load_multi_ohlcv, parse_symbols
|
|
25
|
+
from .runtime.driver import EventBacktest, _configure_logging, _load_ohlcv
|
|
26
|
+
from .runtime.config import resolve_config
|
|
27
|
+
from .runtime.multi_runner import run_portfolio_backtest
|
|
28
|
+
from .runtime.export import finish_run
|
|
29
|
+
from .runtime.result import BacktestResult
|
|
30
|
+
from .runtime.strategy import FunctionStrategy
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"MODE_BACKTEST",
|
|
34
|
+
"ADJUST_NONE",
|
|
35
|
+
"ADJUST_PREV",
|
|
36
|
+
"ADJUST_POST",
|
|
37
|
+
"MATCH_NEXT_OPEN",
|
|
38
|
+
"MATCH_CURRENT_CLOSE",
|
|
39
|
+
"run",
|
|
40
|
+
"run_backtest",
|
|
41
|
+
"default_backtest_window",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _parse_dates(
|
|
46
|
+
backtest_start_time: str | None,
|
|
47
|
+
backtest_end_time: str | None,
|
|
48
|
+
start_date: str | None,
|
|
49
|
+
end_date: str | None,
|
|
50
|
+
) -> tuple[str | None, str | None]:
|
|
51
|
+
"""统一日期参数:backtest_* 与 start_date/end_date 互为别名。"""
|
|
52
|
+
start = (backtest_start_time or start_date or "")[:10] or None
|
|
53
|
+
end = (backtest_end_time or end_date or "")[:10] or None
|
|
54
|
+
return start, end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _resolve_capital(
|
|
58
|
+
backtest_initial_cash: float | None,
|
|
59
|
+
initial_capital: float,
|
|
60
|
+
cash: float | None,
|
|
61
|
+
) -> float:
|
|
62
|
+
if cash is not None:
|
|
63
|
+
return cash
|
|
64
|
+
if backtest_initial_cash is not None:
|
|
65
|
+
return backtest_initial_cash
|
|
66
|
+
return initial_capital
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _resolve_data(
|
|
70
|
+
data: pd.DataFrame | dict[str, pd.DataFrame] | None,
|
|
71
|
+
jcdata_df: pd.DataFrame | None,
|
|
72
|
+
sym_list: list[str],
|
|
73
|
+
) -> tuple[pd.DataFrame | None, dict[str, pd.DataFrame] | None, list[str]]:
|
|
74
|
+
"""
|
|
75
|
+
统一数据入口:data 可为单标的 DataFrame 或多标的 dict。
|
|
76
|
+
jcdata_df 为 jcdata 原始格式,按 sym_list 转换。
|
|
77
|
+
"""
|
|
78
|
+
if isinstance(data, dict):
|
|
79
|
+
if not sym_list:
|
|
80
|
+
sym_list = list(data.keys())
|
|
81
|
+
return None, data, sym_list
|
|
82
|
+
if data is not None:
|
|
83
|
+
return data, None, sym_list
|
|
84
|
+
if jcdata_df is not None:
|
|
85
|
+
if len(sym_list) > 1:
|
|
86
|
+
ohlcv_map = {sym: jcdata_to_ohlcv(jcdata_df, symbol=sym) for sym in sym_list}
|
|
87
|
+
return None, ohlcv_map, sym_list
|
|
88
|
+
sym = sym_list[0] if sym_list else None
|
|
89
|
+
return jcdata_to_ohlcv(jcdata_df, symbol=sym), None, sym_list
|
|
90
|
+
return None, None, sym_list
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def run(
|
|
94
|
+
*,
|
|
95
|
+
mode: str = MODE_BACKTEST,
|
|
96
|
+
strategy_id: str | None = None,
|
|
97
|
+
symbols: str | list[str] | None = None,
|
|
98
|
+
backtest_start_time: str | None = None,
|
|
99
|
+
backtest_end_time: str | None = None,
|
|
100
|
+
start_date: str | None = None,
|
|
101
|
+
end_date: str | None = None,
|
|
102
|
+
backtest_adjust: str = ADJUST_PREV,
|
|
103
|
+
backtest_initial_cash: float | None = None,
|
|
104
|
+
initial_capital: float = 1_000_000,
|
|
105
|
+
cash: float | None = None,
|
|
106
|
+
backtest_commission_ratio: float | None = None,
|
|
107
|
+
commission: float | None = None,
|
|
108
|
+
backtest_slippage_ratio: float = 0.0001,
|
|
109
|
+
slippage: float | None = None,
|
|
110
|
+
backtest_match_mode: int = MATCH_NEXT_OPEN,
|
|
111
|
+
backtest_leverage_ratio: float = 1.0,
|
|
112
|
+
backtest_max_position_ratio: float | None = None,
|
|
113
|
+
backtest_max_single_position_ratio: float | None = None,
|
|
114
|
+
settlement_rule: str | None = None,
|
|
115
|
+
asset_type: str | None = None,
|
|
116
|
+
data: pd.DataFrame | dict[str, pd.DataFrame] | None = None,
|
|
117
|
+
jcdata_df: pd.DataFrame | None = None,
|
|
118
|
+
use_jcdata: bool = False,
|
|
119
|
+
use_mock: bool = False,
|
|
120
|
+
mock_seed: int = 42,
|
|
121
|
+
init_func: Callable | None = None,
|
|
122
|
+
on_bar: Callable | None = None,
|
|
123
|
+
on_backtest_finished: Callable | None = None,
|
|
124
|
+
on_error: Callable | None = None,
|
|
125
|
+
strategy_class: type[Strategy] | None = None,
|
|
126
|
+
strategy_params: dict | None = None,
|
|
127
|
+
debug_level: int = 0,
|
|
128
|
+
deposit: float = 0.0,
|
|
129
|
+
withdrawal: float = 0.0,
|
|
130
|
+
transaction_cost: float = 0.0,
|
|
131
|
+
margin: float | None = None,
|
|
132
|
+
**backtest_kwargs,
|
|
133
|
+
) -> BacktestResult:
|
|
134
|
+
"""
|
|
135
|
+
统一回测入口。
|
|
136
|
+
|
|
137
|
+
symbols: 单标的 str、多标的 list 或逗号分隔字符串,自动选择单标的/组合引擎。
|
|
138
|
+
data: 单标的 OHLCV DataFrame,或多标的 {symbol: DataFrame} dict。
|
|
139
|
+
日期/资金/佣金:backtest_* 与 start_date、initial_capital、commission 等互为别名。
|
|
140
|
+
未传 commission 时使用包内 config.yaml 品种手续费规则。
|
|
141
|
+
|
|
142
|
+
组合仓位:context.get_position(symbol)、order_target_percent(symbol, w)。
|
|
143
|
+
"""
|
|
144
|
+
if mode != MODE_BACKTEST:
|
|
145
|
+
raise ValueError(f"暂不支持 mode={mode}")
|
|
146
|
+
|
|
147
|
+
_configure_logging(debug_level)
|
|
148
|
+
start, end = _parse_dates(backtest_start_time, backtest_end_time, start_date, end_date)
|
|
149
|
+
capital = _resolve_capital(backtest_initial_cash, initial_capital, cash)
|
|
150
|
+
comm = commission if commission is not None else backtest_commission_ratio
|
|
151
|
+
slip = slippage if slippage is not None else backtest_slippage_ratio
|
|
152
|
+
execution_price = "close" if backtest_match_mode == MATCH_CURRENT_CLOSE else "open"
|
|
153
|
+
|
|
154
|
+
sym_list = parse_symbols(symbols)
|
|
155
|
+
single_data, ohlcv_map, sym_list = _resolve_data(data, jcdata_df, sym_list)
|
|
156
|
+
is_portfolio = len(sym_list) > 1 or ohlcv_map is not None
|
|
157
|
+
|
|
158
|
+
config = resolve_config(
|
|
159
|
+
symbol=sym_list[0] if sym_list else None,
|
|
160
|
+
asset_type=asset_type,
|
|
161
|
+
execution_price=execution_price,
|
|
162
|
+
settlement_rule=settlement_rule,
|
|
163
|
+
adjust=backtest_adjust,
|
|
164
|
+
match_mode=backtest_match_mode,
|
|
165
|
+
initial_capital=capital,
|
|
166
|
+
deposit=deposit,
|
|
167
|
+
withdrawal=withdrawal,
|
|
168
|
+
commission=comm,
|
|
169
|
+
slippage=slip,
|
|
170
|
+
transaction_cost=transaction_cost,
|
|
171
|
+
leverage_ratio=backtest_leverage_ratio,
|
|
172
|
+
max_position_ratio=backtest_max_position_ratio,
|
|
173
|
+
max_single_position_ratio=backtest_max_single_position_ratio,
|
|
174
|
+
margin=margin,
|
|
175
|
+
)
|
|
176
|
+
config.trading_pair = ",".join(sym_list) if sym_list else None
|
|
177
|
+
|
|
178
|
+
if is_portfolio:
|
|
179
|
+
if strategy_class is not None:
|
|
180
|
+
raise ValueError("多标的组合模式暂仅支持 init_func/on_bar 函数式策略")
|
|
181
|
+
try:
|
|
182
|
+
if ohlcv_map is None:
|
|
183
|
+
ohlcv_map = load_multi_ohlcv(
|
|
184
|
+
sym_list,
|
|
185
|
+
start or "",
|
|
186
|
+
end or "",
|
|
187
|
+
use_jcdata=use_jcdata,
|
|
188
|
+
use_mock=use_mock,
|
|
189
|
+
mock_seed=mock_seed,
|
|
190
|
+
jcdata_df=jcdata_df,
|
|
191
|
+
adjust=backtest_adjust,
|
|
192
|
+
)
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
if on_error:
|
|
195
|
+
on_error(None, -1, str(exc))
|
|
196
|
+
raise
|
|
197
|
+
|
|
198
|
+
if debug_level >= 1:
|
|
199
|
+
logger.info(
|
|
200
|
+
"jcback portfolio id={} n={} exec={} bars~={}",
|
|
201
|
+
strategy_id,
|
|
202
|
+
len(sym_list),
|
|
203
|
+
config.execution_price,
|
|
204
|
+
len(next(iter(ohlcv_map.values()))) if ohlcv_map else 0,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return run_portfolio_backtest(
|
|
208
|
+
symbols=sym_list,
|
|
209
|
+
config=config,
|
|
210
|
+
ohlcv_map=ohlcv_map,
|
|
211
|
+
init_func=init_func,
|
|
212
|
+
on_bar=on_bar,
|
|
213
|
+
on_backtest_finished=on_backtest_finished,
|
|
214
|
+
debug_level=debug_level,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# 单标的
|
|
218
|
+
try:
|
|
219
|
+
if single_data is not None:
|
|
220
|
+
ohlcv, sym = single_data, (sym_list[0] if sym_list else None)
|
|
221
|
+
else:
|
|
222
|
+
ohlcv, sym = _load_ohlcv(
|
|
223
|
+
data=None,
|
|
224
|
+
jcdata_df=jcdata_df,
|
|
225
|
+
symbol=sym_list[0] if sym_list else None,
|
|
226
|
+
start_date=start,
|
|
227
|
+
end_date=end,
|
|
228
|
+
use_jcdata=use_jcdata,
|
|
229
|
+
use_mock=use_mock,
|
|
230
|
+
mock_seed=mock_seed,
|
|
231
|
+
adjust=backtest_adjust,
|
|
232
|
+
)
|
|
233
|
+
except Exception as exc:
|
|
234
|
+
if on_error:
|
|
235
|
+
on_error(None, -1, str(exc))
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
if not sym:
|
|
239
|
+
sym = sym_list[0] if sym_list else None
|
|
240
|
+
|
|
241
|
+
if strategy_class is not None:
|
|
242
|
+
strat_cls = strategy_class
|
|
243
|
+
elif init_func is not None or on_bar is not None:
|
|
244
|
+
if not sym:
|
|
245
|
+
raise ValueError("函数式策略须指定 symbols")
|
|
246
|
+
strat_cls = FunctionStrategy.attach_callbacks(
|
|
247
|
+
symbol=sym,
|
|
248
|
+
init_func=init_func,
|
|
249
|
+
on_bar_func=on_bar,
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
raise ValueError("须提供 init_func/on_bar 或 strategy_class")
|
|
253
|
+
|
|
254
|
+
if debug_level >= 1:
|
|
255
|
+
logger.info(
|
|
256
|
+
"jcback run id={} pair={} asset={} exec={} settle={} bars={}",
|
|
257
|
+
strategy_id, sym, config.asset_type,
|
|
258
|
+
config.execution_price, config.settlement_rule, len(ohlcv),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
bt = EventBacktest(ohlcv, strat_cls, config=config, debug_level=debug_level, **backtest_kwargs)
|
|
262
|
+
try:
|
|
263
|
+
stats = bt.run(**(strategy_params or {}))
|
|
264
|
+
result = bt.build_result(stats)
|
|
265
|
+
except Exception as exc:
|
|
266
|
+
if on_error:
|
|
267
|
+
on_error(getattr(strat_cls, "_context", None), -2, str(exc))
|
|
268
|
+
raise
|
|
269
|
+
|
|
270
|
+
ctx = getattr(strat_cls, "_context", None)
|
|
271
|
+
if on_backtest_finished:
|
|
272
|
+
on_backtest_finished(ctx, result.indicator)
|
|
273
|
+
|
|
274
|
+
if debug_level >= 1:
|
|
275
|
+
logger.info(
|
|
276
|
+
"jcback done return={:.2f}% trades={}",
|
|
277
|
+
result.indicator.get("return_pct", 0),
|
|
278
|
+
result.indicator.get("num_trades", 0),
|
|
279
|
+
)
|
|
280
|
+
return finish_run(result, strategy_id=strategy_id)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def run_backtest(
|
|
284
|
+
strategy: type[Strategy],
|
|
285
|
+
*,
|
|
286
|
+
data: pd.DataFrame | dict[str, pd.DataFrame] | None = None,
|
|
287
|
+
jcdata_df: pd.DataFrame | None = None,
|
|
288
|
+
symbols: str | list[str] | None = None,
|
|
289
|
+
start_date: str | None = None,
|
|
290
|
+
end_date: str | None = None,
|
|
291
|
+
use_jcdata: bool = False,
|
|
292
|
+
use_mock: bool = False,
|
|
293
|
+
mock_seed: int = 42,
|
|
294
|
+
cash: float | None = None,
|
|
295
|
+
initial_capital: float = 100_000,
|
|
296
|
+
deposit: float = 0.0,
|
|
297
|
+
withdrawal: float = 0.0,
|
|
298
|
+
commission: float | None = None,
|
|
299
|
+
slippage: float = 0.0,
|
|
300
|
+
transaction_cost: float = 0.0,
|
|
301
|
+
leverage_ratio: float = 1.0,
|
|
302
|
+
max_position_ratio: float | None = None,
|
|
303
|
+
max_single_position_ratio: float | None = None,
|
|
304
|
+
margin: float | None = None,
|
|
305
|
+
asset_type: str | None = None,
|
|
306
|
+
execution_price: str = "open",
|
|
307
|
+
settlement_rule: str | None = None,
|
|
308
|
+
adjust: str | None = None,
|
|
309
|
+
match_mode: int | None = None,
|
|
310
|
+
debug_level: int = 0,
|
|
311
|
+
event_handlers: list | None = None,
|
|
312
|
+
finalize_trades: bool = True,
|
|
313
|
+
strategy_params: dict | None = None,
|
|
314
|
+
**backtest_kwargs,
|
|
315
|
+
) -> BacktestResult:
|
|
316
|
+
"""类式策略入口(jcback.Strategy 子类),委托 run() 执行。"""
|
|
317
|
+
if match_mode is None:
|
|
318
|
+
match_mode = MATCH_CURRENT_CLOSE if execution_price.lower() == "close" else MATCH_NEXT_OPEN
|
|
319
|
+
return run(
|
|
320
|
+
strategy_class=strategy,
|
|
321
|
+
symbols=symbols,
|
|
322
|
+
data=data,
|
|
323
|
+
jcdata_df=jcdata_df,
|
|
324
|
+
start_date=start_date,
|
|
325
|
+
end_date=end_date,
|
|
326
|
+
use_jcdata=use_jcdata,
|
|
327
|
+
use_mock=use_mock,
|
|
328
|
+
mock_seed=mock_seed,
|
|
329
|
+
cash=cash,
|
|
330
|
+
initial_capital=initial_capital,
|
|
331
|
+
deposit=deposit,
|
|
332
|
+
withdrawal=withdrawal,
|
|
333
|
+
commission=commission,
|
|
334
|
+
slippage=slippage,
|
|
335
|
+
transaction_cost=transaction_cost,
|
|
336
|
+
backtest_leverage_ratio=leverage_ratio,
|
|
337
|
+
backtest_max_position_ratio=max_position_ratio,
|
|
338
|
+
backtest_max_single_position_ratio=max_single_position_ratio,
|
|
339
|
+
backtest_match_mode=match_mode,
|
|
340
|
+
backtest_adjust=adjust or ADJUST_PREV,
|
|
341
|
+
settlement_rule=settlement_rule,
|
|
342
|
+
asset_type=asset_type,
|
|
343
|
+
debug_level=debug_level,
|
|
344
|
+
strategy_params=strategy_params,
|
|
345
|
+
margin=margin,
|
|
346
|
+
event_handlers=event_handlers,
|
|
347
|
+
finalize_trades=finalize_trades,
|
|
348
|
+
**backtest_kwargs,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def default_backtest_window(days: int = 60) -> tuple[str, str]:
|
|
353
|
+
end = datetime.now()
|
|
354
|
+
start = end - timedelta(days=days)
|
|
355
|
+
return start.strftime("%Y-%m-%d %H:%M:%S"), end.strftime("%Y-%m-%d %H:%M:%S")
|