xqtrader 0.1.0__tar.gz

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 (72) hide show
  1. xqtrader-0.1.0/.claude/skills/create-strategy/SKILL.md +360 -0
  2. xqtrader-0.1.0/.claude/skills/release/SKILL.md +151 -0
  3. xqtrader-0.1.0/.github/workflows/ci.yml +24 -0
  4. xqtrader-0.1.0/.gitignore +211 -0
  5. xqtrader-0.1.0/.python-version +1 -0
  6. xqtrader-0.1.0/AGENTS.md +144 -0
  7. xqtrader-0.1.0/CLAUDE.md +110 -0
  8. xqtrader-0.1.0/LICENSE +21 -0
  9. xqtrader-0.1.0/PKG-INFO +265 -0
  10. xqtrader-0.1.0/README.md +245 -0
  11. xqtrader-0.1.0/README.zh-CN.md +245 -0
  12. xqtrader-0.1.0/cli/__init__.py +0 -0
  13. xqtrader-0.1.0/cli/commands/__init__.py +0 -0
  14. xqtrader-0.1.0/cli/commands/backtest.py +184 -0
  15. xqtrader-0.1.0/cli/commands/strategy.py +152 -0
  16. xqtrader-0.1.0/cli/console.py +117 -0
  17. xqtrader-0.1.0/cli/main.py +21 -0
  18. xqtrader-0.1.0/docs/cli_guide.md +261 -0
  19. xqtrader-0.1.0/docs/publishing_guide.md +61 -0
  20. xqtrader-0.1.0/docs/trading_system_core_requirements.md +2366 -0
  21. xqtrader-0.1.0/docs/trading_system_roadmap.md +50 -0
  22. xqtrader-0.1.0/examples/macd_strategy/macd_strategy.py +236 -0
  23. xqtrader-0.1.0/examples/macd_strategy/run_backtest_events.py +139 -0
  24. xqtrader-0.1.0/examples/macd_strategy/run_backtest_futures.py +139 -0
  25. xqtrader-0.1.0/examples/tmp_check_spot_ohlcv.py +42 -0
  26. xqtrader-0.1.0/main.py +4 -0
  27. xqtrader-0.1.0/pyproject.toml +44 -0
  28. xqtrader-0.1.0/scripts/release.sh +164 -0
  29. xqtrader-0.1.0/setup.cfg +4 -0
  30. xqtrader-0.1.0/tests/__init__.py +0 -0
  31. xqtrader-0.1.0/tests/test_data_center.py +263 -0
  32. xqtrader-0.1.0/tests/test_report_generator.py +341 -0
  33. xqtrader-0.1.0/uv.lock +1250 -0
  34. xqtrader-0.1.0/xqtrader/__init__.py +53 -0
  35. xqtrader-0.1.0/xqtrader/_version.py +34 -0
  36. xqtrader-0.1.0/xqtrader/accounts/__init__.py +0 -0
  37. xqtrader-0.1.0/xqtrader/accounts/base.py +29 -0
  38. xqtrader-0.1.0/xqtrader/accounts/futures.py +56 -0
  39. xqtrader-0.1.0/xqtrader/accounts/simulated.py +18 -0
  40. xqtrader-0.1.0/xqtrader/config/__init__.py +0 -0
  41. xqtrader-0.1.0/xqtrader/data/__init__.py +23 -0
  42. xqtrader-0.1.0/xqtrader/data/adapters/__init__.py +10 -0
  43. xqtrader-0.1.0/xqtrader/data/adapters/base.py +220 -0
  44. xqtrader-0.1.0/xqtrader/data/adapters/binance.py +291 -0
  45. xqtrader-0.1.0/xqtrader/data/adapters/ccxt_adapter.py +294 -0
  46. xqtrader-0.1.0/xqtrader/data/base.py +375 -0
  47. xqtrader-0.1.0/xqtrader/data/cache.py +218 -0
  48. xqtrader-0.1.0/xqtrader/data/circuit_breaker.py +154 -0
  49. xqtrader-0.1.0/xqtrader/engine/__init__.py +0 -0
  50. xqtrader-0.1.0/xqtrader/engine/backtest.py +232 -0
  51. xqtrader-0.1.0/xqtrader/engine/base.py +59 -0
  52. xqtrader-0.1.0/xqtrader/indicators/__init__.py +39 -0
  53. xqtrader-0.1.0/xqtrader/indicators/incremental.py +317 -0
  54. xqtrader-0.1.0/xqtrader/indicators/resampler.py +247 -0
  55. xqtrader-0.1.0/xqtrader/indicators/talipp_adapter.py +568 -0
  56. xqtrader-0.1.0/xqtrader/reports/__init__.py +0 -0
  57. xqtrader-0.1.0/xqtrader/reports/base.py +591 -0
  58. xqtrader-0.1.0/xqtrader/risk/__init__.py +0 -0
  59. xqtrader-0.1.0/xqtrader/risk/base.py +133 -0
  60. xqtrader-0.1.0/xqtrader/strategies/__init__.py +17 -0
  61. xqtrader-0.1.0/xqtrader/strategies/base.py +186 -0
  62. xqtrader-0.1.0/xqtrader/traders/__init__.py +0 -0
  63. xqtrader-0.1.0/xqtrader/traders/base.py +21 -0
  64. xqtrader-0.1.0/xqtrader/traders/events.py +71 -0
  65. xqtrader-0.1.0/xqtrader/traders/futures.py +132 -0
  66. xqtrader-0.1.0/xqtrader/utils/__init__.py +0 -0
  67. xqtrader-0.1.0/xqtrader.egg-info/PKG-INFO +265 -0
  68. xqtrader-0.1.0/xqtrader.egg-info/SOURCES.txt +70 -0
  69. xqtrader-0.1.0/xqtrader.egg-info/dependency_links.txt +1 -0
  70. xqtrader-0.1.0/xqtrader.egg-info/entry_points.txt +3 -0
  71. xqtrader-0.1.0/xqtrader.egg-info/requires.txt +12 -0
  72. xqtrader-0.1.0/xqtrader.egg-info/top_level.txt +2 -0
@@ -0,0 +1,360 @@
1
+ ---
2
+ name: create-strategy
3
+ description: 创建量化交易策略文件。当用户要求创建新策略、编写策略模板、实现交易信号逻辑时使用此技能。
4
+ argument-hint: [策略名称,如 rsi_reversal]
5
+ ---
6
+
7
+ # 创建 XQTrader 交易策略
8
+
9
+ 你正在为 XQTrader 量化交易引擎创建标准化的策略文件。严格遵循以下规范。
10
+
11
+ ## 策略文件结构
12
+
13
+ 每个策略应创建为独立目录,放在 `examples/` 下:
14
+
15
+ ```
16
+ examples/<strategy_name>/
17
+ ├── <strategy_name>.py # 策略实现(必须)
18
+ ├── run_backtest_futures.py # 永续合约回测脚本(按需)
19
+ └── run_backtest_events.py # 事件合约回测脚本(按需)
20
+ ```
21
+
22
+ ## 完整策略模板
23
+
24
+ ```python
25
+ """
26
+ <策略中文名称> - <一句话描述>
27
+
28
+ 策略逻辑:
29
+ - <核心逻辑要点1>
30
+ - <核心逻辑要点2>
31
+
32
+ 适用场景:<震荡行情/趋势行情/...>
33
+ 推荐时间框架:<1h/4h/...>
34
+ """
35
+
36
+ import time
37
+ from collections import deque
38
+ from dataclasses import dataclass, field
39
+ from typing import Any, Dict, List, Optional
40
+
41
+ from xqtrader.strategies.base import (
42
+ BaseStrategy,
43
+ StrategyContext,
44
+ StrategyDataRequirements,
45
+ StrategyResult,
46
+ StrategySignal,
47
+ )
48
+
49
+
50
+ @dataclass
51
+ class MyStrategyConfig:
52
+ """策略参数配置 - 使用 dataclass 便于管理和序列化"""
53
+
54
+ # 指标参数
55
+ period: int = 14
56
+ timeframe: str = "1h"
57
+
58
+ # 信号阈值
59
+ upper_threshold: float = 70.0
60
+ lower_threshold: float = 30.0
61
+
62
+
63
+ class MyStrategy(BaseStrategy):
64
+ """策略类名使用 PascalCase,继承 BaseStrategy"""
65
+
66
+ def __init__(
67
+ self,
68
+ name: str = "my_strategy", # 策略ID,snake_case
69
+ config: Optional[MyStrategyConfig] = None,
70
+ ) -> None:
71
+ super().__init__(
72
+ name=name,
73
+ version="1.0.0",
74
+ description="策略中文描述",
75
+ tags=["trend", "momentum"], # 分类标签
76
+ )
77
+ self.config = config or MyStrategyConfig()
78
+ # 指标ID格式:{指标类型}_{时间框架}
79
+ self._indicator_id = f"rsi_{self.config.timeframe}"
80
+ # 使用 deque 保存历史值用于交叉检测
81
+ self._history: deque = deque(maxlen=10)
82
+
83
+ def get_config(self) -> Dict[str, Any]:
84
+ """返回策略配置,用于日志和报告"""
85
+ return {
86
+ "period": self.config.period,
87
+ "timeframe": self.config.timeframe,
88
+ "upper_threshold": self.config.upper_threshold,
89
+ "lower_threshold": self.config.lower_threshold,
90
+ }
91
+
92
+ def get_data_requirements(
93
+ self, interval: str, config: Optional[Dict[str, Any]] = None
94
+ ) -> StrategyDataRequirements:
95
+ """声明数据需求 - 引擎据此准备数据"""
96
+ min_bars = self.config.period + 10 # 指标周期 + 安全余量
97
+ return StrategyDataRequirements(
98
+ min_bars=min_bars,
99
+ warmup_periods=min_bars,
100
+ )
101
+
102
+ def get_indicator_requirements(self) -> Dict[str, Dict[str, Any]]:
103
+ """声明指标需求 - 引擎自动计算增量指标"""
104
+ return {
105
+ self._indicator_id: {
106
+ "type": "rsi", # 指标类型(见下方支持列表)
107
+ "timeframe": self.config.timeframe,
108
+ "period": self.config.period,
109
+ }
110
+ }
111
+
112
+ def execute(self, context: StrategyContext) -> StrategyResult:
113
+ """
114
+ 策略执行入口 - 每根K线调用一次。
115
+
116
+ Args:
117
+ context: 包含市场数据、指标、账户信息的上下文
118
+
119
+ Returns:
120
+ StrategyResult: 包含信号、指标值、元数据的结果
121
+ """
122
+ start_time = time.time()
123
+
124
+ # ── 第1步:获取增量指标 ──
125
+ incremental = context.incremental_indicators or {}
126
+
127
+ # 检查预热状态(优先按时间框架检查)
128
+ by_timeframe = incremental.get("by_timeframe", {})
129
+ tf_state = by_timeframe.get(self.config.timeframe, {})
130
+ is_warmed_up = tf_state.get(
131
+ "is_warmed_up", incremental.get("is_warmed_up", False)
132
+ )
133
+
134
+ # ── 第2步:预热未完成时返回 HOLD ──
135
+ if not is_warmed_up:
136
+ return StrategyResult(
137
+ signals=[self.create_signal("HOLD", context.symbol, reason="指标预热中")],
138
+ indicators={},
139
+ metadata={"status": "warmup"},
140
+ execution_time=time.time() - start_time,
141
+ success=True,
142
+ )
143
+
144
+ # ── 第3步:提取指标值 ──
145
+ indicator_data = incremental.get("rsi", {}).get(self._indicator_id)
146
+ if indicator_data is None:
147
+ return StrategyResult(
148
+ signals=[self.create_signal("HOLD", context.symbol, reason="指标数据缺失")],
149
+ indicators={},
150
+ metadata={"status": "no_data"},
151
+ execution_time=time.time() - start_time,
152
+ success=True,
153
+ )
154
+
155
+ current_value = indicator_data.get("rsi")
156
+ self._history.append(current_value)
157
+
158
+ # ── 第4步:需要足够历史才能判断 ──
159
+ if len(self._history) < 2:
160
+ return StrategyResult(
161
+ signals=[self.create_signal("HOLD", context.symbol, reason="历史数据不足")],
162
+ indicators={"rsi": current_value},
163
+ metadata={"status": "collecting"},
164
+ execution_time=time.time() - start_time,
165
+ success=True,
166
+ )
167
+
168
+ # ── 第5步:核心信号逻辑 ──
169
+ prev_value = self._history[-2]
170
+ current_price = context.market_data["close"][-1]
171
+
172
+ action = "HOLD"
173
+ reason = "no_signal"
174
+ confidence = 0.5
175
+
176
+ if prev_value >= self.config.upper_threshold and current_value < self.config.upper_threshold:
177
+ action = "SHORT"
178
+ reason = "overbought_reversal"
179
+ confidence = min(0.5 + (prev_value - self.config.upper_threshold) / 100, 1.0)
180
+ elif prev_value <= self.config.lower_threshold and current_value > self.config.lower_threshold:
181
+ action = "LONG"
182
+ reason = "oversold_reversal"
183
+ confidence = min(0.5 + (self.config.lower_threshold - prev_value) / 100, 1.0)
184
+
185
+ # ── 第6步:构建并返回结果 ──
186
+ signal = self.create_signal(
187
+ action=action,
188
+ symbol=context.symbol,
189
+ confidence=confidence,
190
+ reason=reason,
191
+ )
192
+
193
+ return StrategyResult(
194
+ signals=[signal],
195
+ indicators={"rsi": float(current_value)},
196
+ metadata={
197
+ "price": current_price,
198
+ "prev_rsi": float(prev_value),
199
+ },
200
+ execution_time=time.time() - start_time,
201
+ success=True,
202
+ )
203
+ ```
204
+
205
+ ## 回测脚本模板
206
+
207
+ ```python
208
+ """回测脚本 - 永续合约"""
209
+
210
+ import asyncio
211
+ from xqtrader.engine.backtest import BacktestEngine
212
+ from xqtrader.config.backtest_config import BacktestConfig
213
+ from <strategy_module> import MyStrategy, MyStrategyConfig
214
+
215
+
216
+ async def main():
217
+ config = BacktestConfig(
218
+ symbol="BTC/USDT",
219
+ interval="1h",
220
+ initial_capital=10000.0,
221
+ contract_type="futures", # "futures" 或 "events"
222
+ start_date="2025-01-01",
223
+ end_date="2025-03-01",
224
+ )
225
+
226
+ strategy = MyStrategy(config=MyStrategyConfig(period=14))
227
+ engine = BacktestEngine()
228
+ report = await engine.run(strategy, config)
229
+ report.print_summary()
230
+
231
+
232
+ if __name__ == "__main__":
233
+ asyncio.run(main())
234
+ ```
235
+
236
+ ## 核心规范(必须遵守)
237
+
238
+ ### 信号类型
239
+
240
+ | 合约类型 | 开仓 | 平仓 | 观望 |
241
+ |---------|------|------|------|
242
+ | 永续合约 | `LONG` / `SHORT` | `CLOSE_LONG` / `CLOSE_SHORT` / `CLOSE` | `HOLD` |
243
+ | 事件合约 | `UP` / `DOWN` | N/A | `HOLD` |
244
+
245
+ - 优先使用 `LONG/SHORT/CLOSE_LONG/CLOSE_SHORT`,引擎会自动兼容映射
246
+ - `BUY` → `LONG`/`UP`,`SELL` → `SHORT`/`DOWN`
247
+
248
+ ### market_data 访问
249
+
250
+ ```python
251
+ context.market_data["close"][-1] # 最新收盘价
252
+ context.market_data["close"][-10:] # 最近10根K线收盘价
253
+ context.market_data["high"][-1] # 最新最高价
254
+ context.market_data["volume"][-1] # 最新成交量
255
+ context.market_data["timestamps"][-1] # 最新时间戳(毫秒)
256
+ ```
257
+
258
+ 字段:`timestamps`, `open`, `high`, `low`, `close`, `volume`
259
+
260
+ ### incremental_indicators 访问
261
+
262
+ ```python
263
+ incremental = context.incremental_indicators or {}
264
+
265
+ # 全局预热状态
266
+ is_warmed_up = incremental.get("is_warmed_up", False)
267
+
268
+ # 按时间框架的预热状态(推荐)
269
+ tf_state = incremental.get("by_timeframe", {}).get("1h", {})
270
+ is_warmed_up = tf_state.get("is_warmed_up", False)
271
+
272
+ # 获取指标值(按类型和ID)
273
+ macd_data = incremental.get("macd", {}).get("macd_1h")
274
+ rsi_data = incremental.get("rsi", {}).get("rsi_1h")
275
+ ```
276
+
277
+ ### 支持的指标类型
278
+
279
+ | 类别 | 类型 | 关键参数 |
280
+ |------|------|---------|
281
+ | 移动平均 | `sma`, `ema`, `dema`, `tema`, `wma`, `hma`, `kama`, `zlema` | `period` |
282
+ | 动量 | `rsi`, `macd`, `stoch`, `stochrsi`, `cci`, `roc`, `willr`, `tsi`, `ao` | `period` / `fast,slow,signal` |
283
+ | 波动率 | `boll`/`bb`, `atr`, `natr`, `kc`, `dc`, `stddev` | `period`, `std_dev` |
284
+ | 趋势 | `adx`, `aroon`, `psar`, `supertrend` | `period` |
285
+ | 成交量 | `obv`, `vwap`, `adl`, `chaikin` | `period` |
286
+
287
+ ### MACD 指标字段
288
+
289
+ ```python
290
+ macd_data = incremental.get("macd", {}).get("macd_1h")
291
+ macd_data["fast_line"] # MACD 线(别名 "diff")
292
+ macd_data["signal_line"] # 信号线(别名 "dea")
293
+ macd_data["histogram"] # 柱状图(别名 "macd")
294
+ ```
295
+
296
+ ### 预热周期计算
297
+
298
+ | 指标 | 预热公式 | 示例 |
299
+ |------|---------|------|
300
+ | SMA/EMA | `period` | SMA(20) → 20 |
301
+ | RSI | `period + 1` | RSI(14) → 15 |
302
+ | MACD | `slow + signal` | MACD(12,26,9) → 35 |
303
+ | BB | `period` | BB(20) → 20 |
304
+ | ATR | `period` | ATR(14) → 14 |
305
+
306
+ `min_bars` 和 `warmup_periods` = 预热周期 + 安全余量(通常 +10)
307
+
308
+ ### create_signal 参数
309
+
310
+ ```python
311
+ self.create_signal(
312
+ action="LONG", # 必须:信号类型
313
+ symbol=context.symbol, # 必须:交易对
314
+ quantity=0.0, # 可选:数量(0=由引擎决定)
315
+ price=None, # 可选:限价
316
+ stop_loss=None, # 可选:止损价
317
+ take_profit=None, # 可选:止盈价
318
+ confidence=0.8, # 可选:置信度 0-1
319
+ reason="golden_cross", # 可选:原因描述
320
+ )
321
+ ```
322
+
323
+ ### StrategyResult 必填字段
324
+
325
+ ```python
326
+ StrategyResult(
327
+ signals=[signal], # 信号列表(至少一个)
328
+ indicators={...}, # 当前指标快照(用于报告)
329
+ metadata={...}, # 诊断元数据(任意 JSON)
330
+ execution_time=elapsed, # 执行耗时(秒,用 time.time() 计算)
331
+ success=True, # 执行是否成功
332
+ error_message=None, # 仅失败时填写
333
+ )
334
+ ```
335
+
336
+ ## 设计检查清单
337
+
338
+ 创建策略时确认以下事项:
339
+
340
+ - [ ] 继承 `BaseStrategy`,实现 `execute()` 方法
341
+ - [ ] 使用 `@dataclass` 定义策略配置类
342
+ - [ ] 实现 `get_config()` 返回配置字典
343
+ - [ ] 实现 `get_data_requirements()` 声明数据需求
344
+ - [ ] 实现 `get_indicator_requirements()` 声明指标需求
345
+ - [ ] `execute()` 中首先检查预热状态
346
+ - [ ] 预热未完成时返回 `HOLD` 信号
347
+ - [ ] 指标数据缺失时优雅降级为 `HOLD`
348
+ - [ ] 使用 `deque` 保存历史值用于交叉检测
349
+ - [ ] `confidence` 基于指标强度动态计算(0-1)
350
+ - [ ] `reason` 字段描述信号产生原因
351
+ - [ ] `metadata` 包含当前价格等诊断信息
352
+ - [ ] `execution_time` 正确计算并返回
353
+
354
+ ## 参考文件
355
+
356
+ - 策略基类:`xqtrader/strategies/base.py`
357
+ - MACD 策略示例:`examples/macd_strategy/macd_strategy.py`
358
+ - 回测引擎:`xqtrader/engine/backtest.py`
359
+ - 指标引擎:`xqtrader/indicators/incremental.py`
360
+ - 指标适配器:`xqtrader/indicators/talipp_adapter.py`
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: release
3
+ description: 发布 XQTrader 新版本。执行完整的版本发布流程:检查工作区、运行测试、打 tag、构建、发布到 PyPI。当用户要求发布版本、打包发布、release 时使用此技能。
4
+ argument-hint: <版本号> [--dry] [--testpypi],如 0.2.0、0.2.0 --dry、0.2.0 --testpypi
5
+ ---
6
+
7
+ # XQTrader 版本发布流程
8
+
9
+ 你正在执行 XQTrader 的版本发布。严格按照以下步骤顺序执行,每一步都必须通过后才能进入下一步。
10
+
11
+ ## 参数解析
12
+
13
+ 从用户输入中提取:
14
+ - `VERSION`:版本号(必须,SemVer 格式如 `0.2.0`、`1.0.0-beta.1`)
15
+ - `--dry`:仅验证不实际发布(可选)
16
+ - `--testpypi`:发布到 TestPyPI 而非正式 PyPI(可选)
17
+
18
+ 如果用户未提供版本号,询问用户。可参考最近的 git tag 建议下一个版本:
19
+ - Bug 修复:patch +1(如 0.1.0 → 0.1.1)
20
+ - 新功能:minor +1(如 0.1.0 → 0.2.0)
21
+ - 破坏性变更:major +1(如 0.x.y → 1.0.0)
22
+
23
+ ## 第1步:前置检查
24
+
25
+ 依次检查以下条件,任何一项失败则停止并告知用户:
26
+
27
+ ```bash
28
+ # 1.1 检查当前分支(应为 main 或 master)
29
+ git branch --show-current
30
+
31
+ # 1.2 检查工作区是否干净(不能有未提交变更)
32
+ git status --porcelain
33
+
34
+ # 1.3 检查 tag 是否已存在
35
+ git tag -l "v<VERSION>"
36
+
37
+ # 1.4 检查 uv 可用
38
+ uv --version
39
+ ```
40
+
41
+ - 如果分支不是 main/master,**警告用户并确认**是否继续
42
+ - 如果工作区不干净,**停止**,提示用户先 commit
43
+ - 如果 tag 已存在,**停止**,提示用户选择其他版本号
44
+ - 如果是 `--dry` 模式,在每步输出中标注 `[DRY RUN]`
45
+
46
+ ## 第2步:运行测试
47
+
48
+ ```bash
49
+ uv run pytest -q
50
+ ```
51
+
52
+ - 测试必须全部通过才能继续
53
+ - 如果有失败,停止发布并展示失败信息
54
+
55
+ ## 第3步:创建 Git Tag
56
+
57
+ ```bash
58
+ # 非 dry-run 时执行
59
+ git tag -a "v<VERSION>" -m "Release <VERSION>"
60
+ ```
61
+
62
+ - Tag 格式固定为 `v<VERSION>`(如 `v0.2.0`)
63
+ - `--dry` 模式下跳过此步,输出 `[DRY RUN] 跳过 git tag`
64
+
65
+ ## 第4步:清理旧构建
66
+
67
+ ```bash
68
+ rm -rf dist/ build/ *.egg-info xqtrader.egg-info
69
+ ```
70
+
71
+ ## 第5步:构建
72
+
73
+ ```bash
74
+ uv build
75
+ ```
76
+
77
+ 构建完成后验证:
78
+ - `dist/` 下应存在 `.tar.gz`(sdist)和 `.whl`(wheel)两个文件
79
+ - 文件名应包含版本号 `<VERSION>`(如 `xqtrader-0.2.0.tar.gz`)
80
+ - 如果版本号不干净(包含 `.post` 或 `+g` 后缀),说明 tag 未正确关联到当前 commit,**停止并排查**
81
+
82
+ ## 第6步:发布
83
+
84
+ ### 正式 PyPI(默认)
85
+ ```bash
86
+ uv publish
87
+ ```
88
+
89
+ ### TestPyPI(--testpypi 模式)
90
+ ```bash
91
+ uv publish --publish-url https://test.pypi.org/legacy/
92
+ ```
93
+
94
+ ### DRY RUN(--dry 模式)
95
+ 不执行发布,输出验证结果和构建产物信息。
96
+
97
+ **重要:发布到正式 PyPI 前必须向用户确认。**
98
+
99
+ ## 第7步:推送 Tag(可选)
100
+
101
+ 发布成功后,询问用户是否推送 tag 到远程:
102
+
103
+ ```bash
104
+ git push origin "v<VERSION>"
105
+ ```
106
+
107
+ ## 第8步:输出摘要
108
+
109
+ 发布完成后输出:
110
+
111
+ ```
112
+ 发布完成!
113
+ 版本: v<VERSION>
114
+ PyPI: pip install xqtrader==<VERSION>
115
+ 命令: xqtrader --version / xqt --version
116
+ ```
117
+
118
+ 如果是 TestPyPI:
119
+ ```
120
+ 安装测试: pip install -i https://test.pypi.org/simple/ xqtrader==<VERSION>
121
+ ```
122
+
123
+ ## 快捷方式
124
+
125
+ 也可以直接调用发布脚本(脚本内置了所有步骤):
126
+
127
+ ```bash
128
+ ./scripts/release.sh <VERSION> [--dry] [--testpypi]
129
+ ```
130
+
131
+ ## 版本管理规则
132
+
133
+ - 版本号由 `setuptools-scm` 从 git tag 自动生成
134
+ - **不要**手动编辑 `xqtrader/_version.py`,该文件由构建系统自动生成
135
+ - Tag 必须打在干净的 commit 上(无未提交变更),否则版本号会带后缀
136
+ - `pyproject.toml` 中的 `dynamic = ["version"]` 确保版本号从 tag 获取
137
+
138
+ ## 错误处理
139
+
140
+ | 错误 | 原因 | 解决 |
141
+ |------|------|------|
142
+ | 版本号带 `.post0+g...` 后缀 | tag 未在当前 commit 上 | 确保先 commit 所有变更再打 tag |
143
+ | `uv publish` 认证失败 | 未配置 PyPI token | 运行 `uv publish --token <TOKEN>` 或设置 `UV_PUBLISH_TOKEN` 环境变量 |
144
+ | Tag 已存在 | 版本号重复 | 选择新版本号,或 `git tag -d v<VERSION>` 删除后重试 |
145
+ | 测试失败 | 代码问题 | 修复测试后重新发布 |
146
+
147
+ ## 参考文件
148
+
149
+ - 发布脚本:`scripts/release.sh`
150
+ - 项目配置:`pyproject.toml`
151
+ - 版本文件(自动生成):`xqtrader/_version.py`
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: '3.12'
17
+ - name: Install uv
18
+ run: pip install uv
19
+ - name: Sync deps (dev extras)
20
+ run: uv sync --extra dev
21
+ - name: Run tests
22
+ run: uv run pytest
23
+ - name: Build
24
+ run: uv build