mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 16:44:15 +08:00
Initial release v0.1.19
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 653 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 626 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 516 KiB |
@@ -0,0 +1,175 @@
|
||||
# 步骤 1:根据规则生成完整策略
|
||||
|
||||
你是A股量化策略工程师。用户提供策略信息,你输出完整的 `.py` 策略文件(包含参数、信号、告警、评分)。
|
||||
|
||||
**核心原则:贴合用户需求,不要强行套用预设字段。** 数据中已有的指标列和信号列可以用,但如果用户需求涉及自定义概念(如"前高""上次涨停价""N日内某事件后X天"),直接在代码中自行计算,不要为了用已有列而歪曲用户本意。
|
||||
|
||||
**性能原则:优先使用 Polars 语法。** 单日策略用 `pl.Expr` 组合条件;历史窗口策略优先用 `with_columns`、`over("symbol")`、`group_by`、`join`、`filter` 等向量化写法。只有复杂状态机难以用表达式描述时,才使用 `partition_by("symbol")` + `to_dicts()` 的 Python 循环。
|
||||
|
||||
## 输入格式
|
||||
|
||||
用户会提供:
|
||||
- 策略名称(中文)
|
||||
- 策略描述(一句话)
|
||||
- 选股方向:做多 / 做空 / 监控
|
||||
- 策略规则(自然语言描述筛选逻辑)
|
||||
|
||||
## 选择策略模式
|
||||
|
||||
**先分析用户规则,判断使用哪种模式:**
|
||||
|
||||
### 模式 A:单日过滤(filter)
|
||||
当所有条件都是当日指标的比较,且不需要回溯历史时使用。例如:
|
||||
- "收盘价 > ma5 或 ma10"
|
||||
- "RSI < 30"
|
||||
- "放量(量比 > 2)"
|
||||
|
||||
### 模式 B:历史窗口(filter_history)
|
||||
当规则涉及以下任何时序/回溯逻辑时使用:
|
||||
- "最近 N 天内出现过涨停/金叉/某信号"
|
||||
- "涨停后的第 X 天"
|
||||
- "上次涨停的收盘价"、"前高"、"前低"
|
||||
- "连续 N 天阴跌/阳线"
|
||||
- 任何需要多天数据才能判断的条件
|
||||
- 任何用户自定义的、需要从历史数据中计算的概念
|
||||
|
||||
## 你必须完成的全部内容
|
||||
|
||||
输出完整的 Python 策略文件,包含:
|
||||
1. META(含 params、scoring)
|
||||
2. ENTRY_SIGNALS / EXIT_SIGNALS(根据方向和策略逻辑选择)
|
||||
3. STOP_LOSS / MAX_HOLD_DAYS
|
||||
4. ALERTS
|
||||
5. RULES(中文逐条列出核心逻辑)
|
||||
6. filter() 或 filter_history() 函数
|
||||
|
||||
### 模式 A 模板
|
||||
|
||||
```python
|
||||
"""策略描述"""
|
||||
import polars as pl
|
||||
|
||||
META = {
|
||||
"id": "english_id",
|
||||
"name": "用户给的名称",
|
||||
"description": "用户给的描述",
|
||||
"params": [
|
||||
{"id": "param_id", "label": "中文名", "type": "float", "default": 2.0, "min": 0.5, "max": 10.0, "step": 0.1},
|
||||
],
|
||||
"scoring": {
|
||||
"momentum_60d": 0.4, "vol_ratio_5d": 0.3, "change_pct": 0.3,
|
||||
},
|
||||
"order_by": "score",
|
||||
"descending": True,
|
||||
"limit": 100,
|
||||
}
|
||||
|
||||
ENTRY_SIGNALS = ["signal_broken_board_recovery"]
|
||||
EXIT_SIGNALS = ["signal_ma20_breakdown"]
|
||||
STOP_LOSS = -0.05
|
||||
MAX_HOLD_DAYS = 20
|
||||
ALERTS = []
|
||||
|
||||
RULES = """
|
||||
1. 规则一
|
||||
2. 规则二
|
||||
3. 规则三
|
||||
"""
|
||||
|
||||
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
|
||||
param_val = params.get("param_id", 2.0)
|
||||
return (
|
||||
((pl.col("close") > pl.col("ma5")) | (pl.col("close") > pl.col("ma10")))
|
||||
& pl.col("signal_broken_board_recovery").fill_null(False)
|
||||
& (pl.col("vol_ratio_5d") >= param_val)
|
||||
)
|
||||
```
|
||||
|
||||
### 模式 B 模板
|
||||
|
||||
```python
|
||||
"""策略描述"""
|
||||
import polars as pl
|
||||
|
||||
META = {
|
||||
"id": "english_id",
|
||||
"name": "用户给的名称",
|
||||
"description": "用户给的描述",
|
||||
"params": [
|
||||
{"id": "param_id", "label": "中文名", "type": "float", "default": 2.0, "min": 0.5, "max": 10.0, "step": 0.1},
|
||||
],
|
||||
"scoring": {
|
||||
"momentum_60d": 0.4, "vol_ratio_5d": 0.3, "change_pct": 0.3,
|
||||
},
|
||||
"order_by": "score",
|
||||
"descending": True,
|
||||
"limit": 100,
|
||||
}
|
||||
|
||||
LOOKBACK_DAYS = 8 # 根据策略需要的最大回看天数设置
|
||||
|
||||
ENTRY_SIGNALS = ["signal_broken_board_recovery"]
|
||||
EXIT_SIGNALS = ["signal_ma20_breakdown"]
|
||||
STOP_LOSS = -0.05
|
||||
MAX_HOLD_DAYS = 20
|
||||
ALERTS = []
|
||||
|
||||
RULES = """
|
||||
1. 规则一(包含时序逻辑)
|
||||
2. 规则二
|
||||
3. 规则三
|
||||
"""
|
||||
|
||||
def filter_history(df: pl.DataFrame, params: dict) -> pl.DataFrame:
|
||||
if df.is_empty() or "date" not in df.columns:
|
||||
return df
|
||||
|
||||
down_pct = float(params.get("prev_down_pct", -0.02))
|
||||
vol_ratio = float(params.get("volume_ratio", 1.2))
|
||||
tolerance = float(params.get("reversal_tolerance", 0.005))
|
||||
latest = df["date"].max()
|
||||
hist = (
|
||||
df.sort(["symbol", "date"])
|
||||
.with_columns([
|
||||
pl.col("open").shift(1).over("symbol").alias("_prev_open"),
|
||||
pl.col("high").shift(1).over("symbol").alias("_prev_high"),
|
||||
pl.col("close").shift(1).over("symbol").alias("_prev_close"),
|
||||
pl.col("volume").shift(1).over("symbol").alias("_prev_volume"),
|
||||
pl.col("change_pct").shift(1).over("symbol").alias("_prev_change_pct"),
|
||||
])
|
||||
)
|
||||
return hist.filter(pl.col("date") == latest).filter(
|
||||
(pl.col("_prev_close") < pl.col("_prev_open"))
|
||||
& (pl.col("_prev_change_pct") <= down_pct)
|
||||
& (pl.col("close") > pl.col("open"))
|
||||
& (pl.col("close") > pl.col("_prev_open"))
|
||||
& (pl.col("close") >= pl.col("_prev_high") * (1 - tolerance))
|
||||
& (pl.col("volume") >= pl.col("_prev_volume") * vol_ratio)
|
||||
& ((pl.col("close") > pl.col("ma5")) | (pl.col("close") > pl.col("ma10")))
|
||||
)
|
||||
```
|
||||
|
||||
如果是极复杂状态机,Polars 表达式很难清楚表达时,才使用 `partition_by("symbol")` + `to_dicts()` 逐股票分析。
|
||||
|
||||
## 信号匹配指南(参考)
|
||||
|
||||
根据用户的选股方向和策略逻辑,从可用信号中选择合适的买入/卖出信号。信号列仅供参考,不强求使用。
|
||||
|
||||
| 方向 | 推荐买入信号 | 推荐卖出信号 |
|
||||
|------|-------------|-------------|
|
||||
| 做多 | signal_n_day_high, signal_ma20_breakout, signal_ma_golden_5_20, signal_ma_golden_20_60, signal_macd_golden, signal_boll_breakout_upper, signal_limit_up, signal_limit_down_recovery | signal_ma20_breakdown, signal_macd_dead, signal_n_day_low |
|
||||
| 做空 | signal_n_day_low, signal_boll_breakdown_lower | signal_n_day_high, signal_ma_golden_5_20 |
|
||||
| 监控 | 两者都选 | 两者都选 |
|
||||
|
||||
## 规则
|
||||
|
||||
1. 用户可能调节的数值阈值通过 `META["params"]` 暴露,filter()/filter_history() 中用 `params.get()` 读取;公式常数、固定窗口边界不必强行参数化
|
||||
2. 信号列使用 `.fill_null(False)` 处理空值
|
||||
3. `filter()` 只返回 `pl.Expr`,`filter_history()` 返回筛选后的 `DataFrame`
|
||||
4. scoring 权重总和 = 1.0
|
||||
5. `name` 使用用户输入,`description` 写一句简洁摘要
|
||||
6. **必须生成 RULES**:格式 `RULES = """\n1. 规则一\n2. 规则二\n3. 规则三\n"""`,用中文逐条列出核心筛选逻辑(至少 3 条),这是用户审阅策略的唯一依据,务必准确完整
|
||||
7. **贴合用户需求**:不要为了使用已有字段而改变用户本意。用户说"前高"就是"前高",需要自己算就自己算;用户说"最近涨停后的收盘价"就从历史数据中找,不要用其他近似值替代
|
||||
8. **输出前自我检查**:确认 RULES 已生成、Python 语法正确、括号匹配、引号闭合
|
||||
9. **优先 Polars**:不要默认生成逐行/逐股 Python 循环;能用表达式、窗口、聚合、join 完成时就用 Polars 语法
|
||||
10. 直接输出 Python 代码,不要解释文字
|
||||
@@ -0,0 +1,34 @@
|
||||
# 步骤 2:修改策略任意部分
|
||||
|
||||
你是A股量化策略工程师。根据用户指令修改策略代码的任意部分。
|
||||
|
||||
## 输入格式
|
||||
|
||||
分两部分提供:
|
||||
1. 当前策略的完整 Python 代码
|
||||
2. 用户的修改指令(自然语言)
|
||||
|
||||
## 输出要求
|
||||
|
||||
只输出修改后的完整 Python 代码,不要解释。
|
||||
|
||||
## 你应该做的事
|
||||
|
||||
- 增/删/改参数 → 更新 META["params"],同步修改 filter()
|
||||
- 调整信号 → 更新 ENTRY_SIGNALS / EXIT_SIGNALS
|
||||
- 修改止损/持有 → 更新 STOP_LOSS / MAX_HOLD_DAYS
|
||||
- 增减告警 → 更新 ALERTS
|
||||
- 调整评分 → 更新 META["scoring"],权重总和保持 100
|
||||
- 修改筛选逻辑 → 更新 filter();如果新增/删除了历史回溯逻辑,同步改为或移除 filter_history() 与 LOOKBACK_DAYS
|
||||
|
||||
## 规则
|
||||
|
||||
1. 保持策略文件结构完整,不丢失任何已有字段(包括 RULES)
|
||||
2. 删除参数后 filter() 中用原 default 值代替
|
||||
3. 新增参数要有 type、label、default、min、max、step
|
||||
4. 删除信号时 ENTRY_SIGNALS / EXIT_SIGNALS 至少保留一个
|
||||
5. 如果修改了筛选逻辑,同步更新 RULES 中的对应条目
|
||||
6. 用户可能调节的阈值才需要放入 META["params"];公式常数、固定窗口边界不必强行参数化
|
||||
7. 优先使用 Polars 表达式、窗口函数、聚合和 join,不要默认改成逐行/逐股 Python 循环
|
||||
8. **输出前自我检查**:完整通读修改后的代码,确认 Python 语法正确、括号匹配、引号闭合、缩进一致。有错误直接修正再输出。
|
||||
9. 直接输出完整 Python 代码
|
||||
@@ -0,0 +1,153 @@
|
||||
# 两步创建示例:强势反包策略
|
||||
|
||||
本文演示从零创建一个自定义策略的完整流程。
|
||||
|
||||
---
|
||||
|
||||
## Step 1:填写规则
|
||||
|
||||
用户在创建对话框填写:
|
||||
|
||||
| 字段 | 填写内容 |
|
||||
|------|---------|
|
||||
| 名称 | **强势反包** |
|
||||
| 描述 | 筛选前日阴线下跌、今日放量阳线反包的短线强势股 |
|
||||
| 方向 | 做多 |
|
||||
| 规则 | 前一交易日为明显阴线且跌幅不低于2%,今日阳线收盘反包前一日实体,收盘价接近或高于前一日高点,成交量较前一日放大1.2倍以上,当前 close > ma5 或 close > ma10;使用 filter_history,并优先用 Polars shift/with_columns/filter 实现。 |
|
||||
|
||||
点击「AI 生成」,AI 返回完整策略代码(含参数、信号、评分、告警):
|
||||
|
||||
```python
|
||||
"""强势反包 — 前日阴线下跌 + 今日放量阳线反包"""
|
||||
import polars as pl
|
||||
|
||||
META = {
|
||||
"id": "strong_reversal",
|
||||
"name": "强势反包",
|
||||
"description": "筛选前日阴线下跌、今日放量阳线反包的短线强势股",
|
||||
"tags": ["反包", "短线", "放量"],
|
||||
"basic_filter": {
|
||||
"price_min": 3,
|
||||
"price_max": 200,
|
||||
"market_cap_min": 10e8,
|
||||
"amount_min": 0.5e8,
|
||||
"exclude_st": True,
|
||||
"exclude_new_days": 30,
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"id": "prev_down_pct",
|
||||
"label": "前日最大跌幅",
|
||||
"type": "float",
|
||||
"default": -0.02,
|
||||
"min": -0.10,
|
||||
"max": -0.005,
|
||||
"step": 0.005,
|
||||
},
|
||||
{
|
||||
"id": "volume_ratio",
|
||||
"label": "成交量放大倍数",
|
||||
"type": "float",
|
||||
"default": 1.2,
|
||||
"min": 1.0,
|
||||
"max": 5.0,
|
||||
"step": 0.1,
|
||||
},
|
||||
{
|
||||
"id": "reversal_tolerance",
|
||||
"label": "反包容忍误差",
|
||||
"type": "float",
|
||||
"default": 0.005,
|
||||
"min": 0.0,
|
||||
"max": 0.03,
|
||||
"step": 0.005,
|
||||
},
|
||||
],
|
||||
"scoring": {
|
||||
"change_pct": 0.4,
|
||||
"vol_ratio_5d": 0.3,
|
||||
"momentum_5d": 0.3,
|
||||
},
|
||||
"order_by": "score",
|
||||
"descending": True,
|
||||
"limit": 100,
|
||||
}
|
||||
|
||||
LOOKBACK_DAYS = 2
|
||||
|
||||
ENTRY_SIGNALS = ["signal_broken_board_recovery"]
|
||||
EXIT_SIGNALS = ["signal_ma20_breakdown"]
|
||||
STOP_LOSS = -0.05
|
||||
MAX_HOLD_DAYS = 10
|
||||
ALERTS = [
|
||||
{"field": "signal_broken_board_recovery", "message": "反包信号"},
|
||||
]
|
||||
|
||||
RULES = """
|
||||
1. 前一交易日为阴线,且跌幅不小于设定阈值
|
||||
2. 今日为阳线,收盘价收复前一日开盘价并接近或突破前一日高点
|
||||
3. 今日成交量较前一日明显放大,且收盘价站上 MA5 或 MA10
|
||||
"""
|
||||
|
||||
|
||||
def filter_history(df: pl.DataFrame, params: dict) -> pl.DataFrame:
|
||||
if df.is_empty() or "date" not in df.columns:
|
||||
return df
|
||||
|
||||
down_pct = float(params.get("prev_down_pct", -0.02))
|
||||
vol_ratio = float(params.get("volume_ratio", 1.2))
|
||||
tolerance = float(params.get("reversal_tolerance", 0.005))
|
||||
latest = df["date"].max()
|
||||
hist = (
|
||||
df.sort(["symbol", "date"])
|
||||
.with_columns([
|
||||
pl.col("open").shift(1).over("symbol").alias("_prev_open"),
|
||||
pl.col("high").shift(1).over("symbol").alias("_prev_high"),
|
||||
pl.col("close").shift(1).over("symbol").alias("_prev_close"),
|
||||
pl.col("volume").shift(1).over("symbol").alias("_prev_volume"),
|
||||
pl.col("change_pct").shift(1).over("symbol").alias("_prev_change_pct"),
|
||||
])
|
||||
)
|
||||
return hist.filter(pl.col("date") == latest).filter(
|
||||
(pl.col("_prev_close") < pl.col("_prev_open"))
|
||||
& (pl.col("_prev_change_pct") <= down_pct)
|
||||
& (pl.col("close") > pl.col("open"))
|
||||
& (pl.col("close") > pl.col("_prev_open"))
|
||||
& (pl.col("close") >= pl.col("_prev_high") * (1 - tolerance))
|
||||
& (pl.col("volume") >= pl.col("_prev_volume") * vol_ratio)
|
||||
& ((pl.col("close") > pl.col("ma5")) | (pl.col("close") > pl.col("ma10")))
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2:预览 + 指令修改
|
||||
|
||||
进入第二步,显示完整的策略预览和指令输入框。
|
||||
|
||||
用户如果觉得反包条件太严格,可以输入「把前日跌幅放宽到 -1.5%,反包前高允许 1% 误差」→ 点 AI 修改。AI 更新 `params` 默认值和 `filter_history()` 逻辑。
|
||||
|
||||
确认无误后点「保存策略」→ 策略池中出现。
|
||||
|
||||
---
|
||||
|
||||
## 后续使用
|
||||
|
||||
打开策略配置,**基础参数**和**策略参数**分别独立:
|
||||
|
||||
```
|
||||
┌─ 配置:强势反包 ──────────────────────────┐
|
||||
│ 名称 [强势反包 ] 显示上限 [30]│
|
||||
│ │
|
||||
│ 📊 基础参数 [启用 ●] │
|
||||
│ 价格 [3]~[200]元 排除ST、新股 │
|
||||
│ 最低成交额 [5000万] │
|
||||
│ │
|
||||
│ ⚙ 策略参数 │
|
||||
│ 前日最大跌幅 [-0.02] │
|
||||
│ 成交量放大倍数 [1.2] │
|
||||
│ 反包容忍误差 [0.005] │
|
||||
│ │
|
||||
│ ⭐ 评分权重 📈 交易参数 │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
@@ -0,0 +1,332 @@
|
||||
# 策略开发指南
|
||||
|
||||
本文档是策略开发的完整参考。人类开发者参考它编写策略,AI 读取它生成策略代码。
|
||||
|
||||
## 1. 策略文件格式
|
||||
|
||||
每个策略是一个 Python 文件,放在以下目录:
|
||||
|
||||
- 内置策略: `backend/app/strategy/builtin/`
|
||||
- 自定义策略: `data/strategies/custom/`,建议文件名和 ID 使用 `custom_时间戳`
|
||||
- AI 生成策略: `data/strategies/ai/`,文件名和 ID 使用 `ai_时间戳`
|
||||
|
||||
## 2. 文件结构模板
|
||||
|
||||
```python
|
||||
"""策略简短描述"""
|
||||
import polars as pl
|
||||
|
||||
META = {
|
||||
"id": "strategy_id", # 英文ID, 唯一, 文件名同名;自定义策略建议 custom_时间戳
|
||||
"name": "策略中文名", # 显示名称
|
||||
"description": "策略详细描述", # 一句话说明策略逻辑
|
||||
"tags": ["标签1", "标签2"], # 分类标签
|
||||
|
||||
# 基础过滤参数 (Stage 1, 引擎统一处理)
|
||||
"basic_filter": {
|
||||
"price_min": 5, # 最低价格
|
||||
"price_max": 200, # 最高价格
|
||||
"market_cap_min": 20e8, # 最小总市值 (元)
|
||||
"amount_min": 1e8, # 最小成交额 (元)
|
||||
"exclude_st": True, # 排除 ST/*ST/退市
|
||||
"exclude_new_days": 60, # 排除上市N天内新股
|
||||
},
|
||||
|
||||
# 策略参数 (Stage 2, filter() 使用, 前端渲染为表单)
|
||||
# 用户可能调节的阈值通过 params 暴露;公式常数、固定窗口边界不必强行参数化
|
||||
"params": [
|
||||
{
|
||||
"id": "param_id", # 参数ID, filter() 中 params.get("param_id")
|
||||
"label": "参数显示名", # 前端显示
|
||||
"type": "float", # float | int | select
|
||||
"default": 2.0, # 默认值
|
||||
"min": 0.5, # 最小值 (float/int)
|
||||
"max": 10.0, # 最大值 (float/int)
|
||||
"step": 0.1, # 步长
|
||||
# select 类型用 options:
|
||||
# "options": ["ma5", "ma10", "ma20", "ma60"],
|
||||
},
|
||||
],
|
||||
|
||||
# 评分权重 (用于排序, 权重总和 = 1.0)
|
||||
"scoring": {
|
||||
"momentum_60d": 0.4,
|
||||
"vol_ratio_5d": 0.3,
|
||||
"change_pct": 0.3,
|
||||
},
|
||||
|
||||
"order_by": "score", # 排序字段, 通常用 "score"
|
||||
"descending": True, # True = 从高到低
|
||||
"limit": 100, # 最多返回条数
|
||||
}
|
||||
|
||||
# 买入信号 (回测 + 监控用, 对应 enriched 表的信号列名)
|
||||
ENTRY_SIGNALS = ["signal_broken_board_recovery"]
|
||||
|
||||
# 卖出信号
|
||||
EXIT_SIGNALS = ["signal_ma20_breakdown"]
|
||||
|
||||
# 止损 (负数, 如 -0.08 = -8%)
|
||||
STOP_LOSS = -0.08
|
||||
|
||||
# 最长持有天数
|
||||
MAX_HOLD_DAYS = 20
|
||||
|
||||
# 提醒条件 (监控用)
|
||||
ALERTS = [
|
||||
{"field": "signal_broken_board_recovery", "message": "反包信号"},
|
||||
{"field": "rsi_14", "op": ">", "value": 80, "message": "RSI超买预警"},
|
||||
]
|
||||
|
||||
|
||||
# 策略规则(人类可读,逐条编号,至少 3 条)
|
||||
RULES = """
|
||||
1. 规则描述一
|
||||
2. 规则描述二
|
||||
3. 规则描述三
|
||||
"""
|
||||
|
||||
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
|
||||
"""策略核心过滤逻辑。
|
||||
|
||||
df: Stage 1 基础过滤后的 enriched 数据
|
||||
params: META.params 中定义的参数值 (用户可在前端覆盖)
|
||||
|
||||
返回: Polars 布尔表达式 (pl.Expr)
|
||||
"""
|
||||
vol_min = params.get("vol_ratio_min", 2.0)
|
||||
return (
|
||||
((pl.col("close") > pl.col("ma5")) | (pl.col("close") > pl.col("ma10")))
|
||||
& pl.col("signal_broken_board_recovery").fill_null(False)
|
||||
& (pl.col("vol_ratio_5d") >= vol_min)
|
||||
)
|
||||
```
|
||||
|
||||
### 历史窗口策略(filter_history)
|
||||
|
||||
普通 `filter()` 只接收当前日期的单日数据。当策略需要以下逻辑时,必须使用 `filter_history()`:
|
||||
|
||||
- "最近 N 天内出现过某个事件"(如涨停、金叉)
|
||||
- "某个事件发生后的第 X 天"(如涨停后放量下跌)
|
||||
- "前高 / 前低 / 上次某事件的价格"等需要回溯历史的自定义字段
|
||||
- 任何需要多日数据才能计算的时序逻辑
|
||||
|
||||
**不需要** `filter_history()` 的场景:只用当日指标列做比较(如 close > ma60、rsi_14 < 30)。
|
||||
|
||||
```python
|
||||
LOOKBACK_DAYS = 8 # 回看交易日数,根据策略需要设置
|
||||
|
||||
def filter_history(df: pl.DataFrame, params: dict) -> pl.DataFrame:
|
||||
"""df 包含目标日期之前 LOOKBACK_DAYS 个交易日的数据(所有股票混合)。
|
||||
每行包含 symbol, date 及所有指标列/信号列。
|
||||
|
||||
返回值: 筛选后的 DataFrame,引擎会最终保留目标日期行。
|
||||
"""
|
||||
if df.is_empty() or "date" not in df.columns:
|
||||
return df
|
||||
|
||||
down_pct = float(params.get("prev_down_pct", -0.02))
|
||||
vol_ratio = float(params.get("volume_ratio", 1.2))
|
||||
tolerance = float(params.get("reversal_tolerance", 0.005))
|
||||
latest = df["date"].max()
|
||||
|
||||
# 示例: 前日明显阴线下跌,今日放量阳线反包前日实体
|
||||
hist = (
|
||||
df.sort(["symbol", "date"])
|
||||
.with_columns([
|
||||
pl.col("open").shift(1).over("symbol").alias("_prev_open"),
|
||||
pl.col("high").shift(1).over("symbol").alias("_prev_high"),
|
||||
pl.col("close").shift(1).over("symbol").alias("_prev_close"),
|
||||
pl.col("volume").shift(1).over("symbol").alias("_prev_volume"),
|
||||
pl.col("change_pct").shift(1).over("symbol").alias("_prev_change_pct"),
|
||||
])
|
||||
)
|
||||
|
||||
return hist.filter(pl.col("date") == latest).filter(
|
||||
(pl.col("_prev_close") < pl.col("_prev_open"))
|
||||
& (pl.col("_prev_change_pct") <= down_pct)
|
||||
& (pl.col("close") > pl.col("open"))
|
||||
& (pl.col("close") > pl.col("_prev_open"))
|
||||
& (pl.col("close") >= pl.col("_prev_high") * (1 - tolerance))
|
||||
& (pl.col("volume") >= pl.col("_prev_volume") * vol_ratio)
|
||||
& ((pl.col("close") > pl.col("ma5")) | (pl.col("close") > pl.col("ma10")))
|
||||
)
|
||||
```
|
||||
|
||||
**关键要点:**
|
||||
- `LOOKBACK_DAYS` 决定引擎加载多少天的数据,设为策略逻辑需要的最大回看天数
|
||||
- 优先使用 Polars 的 `with_columns`、`over("symbol")`、`group_by`、`join`、`filter` 实现历史逻辑,避免把数据转成 Python list/dict 循环
|
||||
- 只有遇到表达式难以描述的复杂状态机时,才使用 `partition_by("symbol")` + `to_dicts()` 逐股票分析
|
||||
- 返回的 DataFrame 必须只包含目标日期行,通过 `pl.col("date") == latest` 过滤
|
||||
- 未声明 `filter_history()` 的策略走普通 `filter()` 路径,不受影响
|
||||
|
||||
## 3. 常用指标列(参考,可直接使用)
|
||||
|
||||
以下列在数据中已预计算,可直接引用。**但如果这些列无法满足策略需求,可以不用,自行在 `filter_history()` 中基于 enriched 表的数据(已复权,含所有指标列和信号列)计算任何需要的字段。**
|
||||
|
||||
### 价格相关
|
||||
|
||||
| 列名 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| open, high, low, close | float | OHLCV 开高低收 |
|
||||
| prev_close | float | 昨收价 |
|
||||
| change_pct | float | 涨跌幅 (如 0.032 = +3.2%) |
|
||||
| change_amount | float | 涨跌额 |
|
||||
| amount | float | 成交额 |
|
||||
| amplitude | float | 振幅 |
|
||||
|
||||
### 均线
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| ma5, ma10, ma20, ma30, ma60 | 简单移动均线 |
|
||||
| ema5, ema10, ema20, ema30, ema60 | 指数移动均线 |
|
||||
|
||||
### 技术指标
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| macd_dif | MACD DIF 线 |
|
||||
| macd_dea | MACD DEA 线 |
|
||||
| macd_hist | MACD 柱状 |
|
||||
| boll_upper, boll_lower | 布林带上/下轨 |
|
||||
| kdj_k, kdj_d, kdj_j | KDJ 指标 |
|
||||
| rsi_6, rsi_14, rsi_24 | RSI 相对强弱 |
|
||||
| atr_14 | 平均真实波幅 |
|
||||
|
||||
### 量能
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| volume | 成交量 |
|
||||
| vol_ma5, vol_ma10 | 成交量均线 |
|
||||
| vol_ratio_5d | 5日量比 |
|
||||
| turnover_rate | 换手率 |
|
||||
|
||||
### 动量与波动
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| momentum_5d / 10d / 20d / 30d / 60d | N日涨幅 |
|
||||
| annual_vol_20d | 20日年化波动率 |
|
||||
| high_60d, low_60d | 60日最高/最低价 |
|
||||
|
||||
### 涨跌停
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| consecutive_limit_ups | 连续涨停天数 |
|
||||
| consecutive_limit_downs | 连续跌停天数 |
|
||||
|
||||
## 4. 常用信号列(参考)
|
||||
|
||||
信号列是布尔值,使用时用 `.fill_null(False)` 处理空值。同样仅供参考,不要求必须使用。
|
||||
|
||||
信号列是布尔值,**必须**使用 `.fill_null(False)` 处理空值。
|
||||
|
||||
| 列名 | 方向 | 说明 |
|
||||
|------|------|------|
|
||||
| signal_ma_golden_5_20 | 买入 | MA5 上穿 MA20 |
|
||||
| signal_ma_dead_5_20 | 卖出 | MA5 下穿 MA20 |
|
||||
| signal_ma_golden_20_60 | 买入 | MA20 上穿 MA60 |
|
||||
| signal_macd_golden | 买入 | MACD 金叉 |
|
||||
| signal_macd_dead | 卖出 | MACD 死叉 |
|
||||
| signal_ma20_breakout | 买入 | 突破 MA20 |
|
||||
| signal_ma20_breakdown | 卖出 | 跌破 MA20 |
|
||||
| signal_n_day_high | 买入 | 60日新高 |
|
||||
| signal_n_day_low | 卖出 | 60日新低 |
|
||||
| signal_boll_breakout_upper | 中性 | 突破布林上轨 |
|
||||
| signal_boll_breakdown_lower | 中性 | 跌破布林下轨 |
|
||||
| signal_volume_surge | 中性 | 放量 |
|
||||
| signal_limit_up | 买入 | 涨停 |
|
||||
| signal_limit_down | 卖出 | 跌停 |
|
||||
| signal_limit_down_recovery | 买入 | 跌停翘板 |
|
||||
|
||||
## 5. 规则
|
||||
|
||||
1. `filter()` 必须返回 `pl.Expr` (用 `&` `|` 组合布尔表达式);`filter_history()` 返回筛选后的 `DataFrame`
|
||||
2. 信号列使用 `.fill_null(False)` 处理空值
|
||||
3. 用户可能调节的数值阈值通过 `params` 暴露;公式常数、固定窗口边界、一次性内部变量不必强行参数化
|
||||
4. `scoring` 权重总和必须为 1.0
|
||||
5. 遵循 A 股 T+1 规则 (当日买入次日才能卖出)
|
||||
6. 只允许 `import polars as pl`,禁止 import 其他模块
|
||||
7. 禁止使用 `open()`, `exec()`, `eval()`, `os`, `sys`, `subprocess`
|
||||
8. **贴合用户需求优先**:第3/4节的指标列和信号列仅供参考,能用则用;如果用户需求需要自定义计算(如"前高""上次涨停价""N日内某个事件后X天"),直接在 `filter_history()` 中自行设计和计算,不需要局限于已有列
|
||||
9. `filter_history()` 中优先用 Polars 向量化语法;仅在复杂状态机无法清晰表达时,才用 `partition_by("symbol")` 逐股票分析
|
||||
|
||||
## 6. 策略示例
|
||||
|
||||
### 强势反包
|
||||
|
||||
```python
|
||||
"""强势反包 — 前日阴线下跌 + 今日放量阳线反包"""
|
||||
import polars as pl
|
||||
|
||||
META = {
|
||||
"id": "strong_reversal",
|
||||
"name": "强势反包",
|
||||
"description": "前一日明显阴线下跌,今日放量阳线收复前一日阴线实体",
|
||||
"tags": ["反包", "短线", "放量"],
|
||||
"basic_filter": {
|
||||
"price_min": 3, "price_max": 200,
|
||||
"market_cap_min": 10e8, "amount_min": 0.5e8,
|
||||
"exclude_st": True, "exclude_new_days": 30,
|
||||
},
|
||||
"params": [
|
||||
{"id": "prev_down_pct", "label": "前日最大跌幅", "type": "float",
|
||||
"default": -0.02, "min": -0.10, "max": -0.005, "step": 0.005},
|
||||
{"id": "volume_ratio", "label": "成交量放大倍数", "type": "float",
|
||||
"default": 1.2, "min": 1.0, "max": 5.0, "step": 0.1},
|
||||
{"id": "reversal_tolerance", "label": "反包容忍误差", "type": "float",
|
||||
"default": 0.005, "min": 0.0, "max": 0.03, "step": 0.005},
|
||||
],
|
||||
"scoring": {"change_pct": 0.4, "vol_ratio_5d": 0.3, "momentum_5d": 0.3},
|
||||
"order_by": "score", "descending": True, "limit": 100,
|
||||
}
|
||||
|
||||
LOOKBACK_DAYS = 2
|
||||
|
||||
ENTRY_SIGNALS = ["signal_broken_board_recovery"]
|
||||
EXIT_SIGNALS = ["signal_ma20_breakdown"]
|
||||
STOP_LOSS = -0.05
|
||||
MAX_HOLD_DAYS = 10
|
||||
ALERTS = [{"field": "signal_broken_board_recovery", "message": "反包信号"}]
|
||||
|
||||
RULES = """
|
||||
1. 前一交易日为阴线,且跌幅不小于设定阈值
|
||||
2. 今日为阳线,收盘价收复前一日开盘价并接近或突破前一日高点
|
||||
3. 今日成交量较前一日明显放大,且收盘价站上 MA5 或 MA10
|
||||
"""
|
||||
|
||||
def filter_history(df: pl.DataFrame, params: dict) -> pl.DataFrame:
|
||||
if df.is_empty() or "date" not in df.columns:
|
||||
return df
|
||||
|
||||
down_pct = float(params.get("prev_down_pct", -0.02))
|
||||
vol_ratio = float(params.get("volume_ratio", 1.2))
|
||||
tolerance = float(params.get("reversal_tolerance", 0.005))
|
||||
latest = df["date"].max()
|
||||
hist = (
|
||||
df.sort(["symbol", "date"])
|
||||
.with_columns([
|
||||
pl.col("open").shift(1).over("symbol").alias("_prev_open"),
|
||||
pl.col("high").shift(1).over("symbol").alias("_prev_high"),
|
||||
pl.col("close").shift(1).over("symbol").alias("_prev_close"),
|
||||
pl.col("volume").shift(1).over("symbol").alias("_prev_volume"),
|
||||
pl.col("change_pct").shift(1).over("symbol").alias("_prev_change_pct"),
|
||||
])
|
||||
)
|
||||
return hist.filter(pl.col("date") == latest).filter(
|
||||
(pl.col("_prev_close") < pl.col("_prev_open"))
|
||||
& (pl.col("_prev_change_pct") <= down_pct)
|
||||
& (pl.col("close") > pl.col("open"))
|
||||
& (pl.col("close") > pl.col("_prev_open"))
|
||||
& (pl.col("close") >= pl.col("_prev_high") * (1 - tolerance))
|
||||
& (pl.col("volume") >= pl.col("_prev_volume") * vol_ratio)
|
||||
& ((pl.col("close") > pl.col("ma5")) | (pl.col("close") > pl.col("ma10")))
|
||||
)
|
||||
```
|
||||
|
||||
## 7. 完整示例
|
||||
|
||||
见 [strategy-example.md](./strategy-example.md) — 从零创建强势反包策略的三步完整演示。
|
||||
Reference in New Issue
Block a user