feat(strategy): 新增 7 个矩阵原生内置策略, 公开计数 18→25

- 趋势/形态: 均线粘合突破 · 平台整理突破 · 放量创60日新高
- 量价/涨停: 涨停基因活跃股 (max_change_pct 参数过滤当日已大涨)
- 反转/波动: MACD 零下回升 · 长下影反击 (close_position>=0.5 兼容假阴线) · RSI 中轴回踩
- 阈值统一小数制口径 (change_pct/momentum 除以 100), 一致性测试计数 26
This commit is contained in:
shy3130
2026-08-31 22:30:44 +08:00
parent be799abfa0
commit 58f8d7b883
10 changed files with 660 additions and 8 deletions
+2 -2
View File
@@ -43,7 +43,7 @@
| 模块         | 一句话 | 详见    | | 模块         | 一句话 | 详见    |
| :--------------- | :--------------------------------------------------------------------- | :-------------------------------- | | :--------------- | :--------------------------------------------------------------------- | :-------------------------------- |
| 🔀 **能力路由** | 多数据集(日K/除权/实时/分钟/盘口/财务,持续扩展)按源能力独立路由,任选组合 | [custom-data-source.md](./docs/custom-data-source.md) | | 🔀 **能力路由** | 多数据集(日K/除权/实时/分钟/盘口/财务,持续扩展)按源能力独立路由,任选组合 | [custom-data-source.md](./docs/custom-data-source.md) |
| 🔍 **选股引擎** | 18 个内置策略 + 分钟策略 + 自定义信号 + AI 生成,Polars 毫秒级扫全 A 股 | [strategy.md](./docs/strategy.md) | | 🔍 **选股引擎** | 25 个内置策略 + 分钟策略 + 自定义信号 + AI 生成,Polars 毫秒级扫全 A 股 | [strategy.md](./docs/strategy.md) |
| 📊 **指标流水线** | MA/EMA/MACD/RSI/KDJ/布林/量比等 68 列指标与信号,一次扫表落盘 enriched Parquet | [features.md](./docs/features.md) | | 📊 **指标流水线** | MA/EMA/MACD/RSI/KDJ/布林/量比等 68 列指标与信号,一次扫表落盘 enriched Parquet | [features.md](./docs/features.md) |
| 🧪 **回测研究** | 因子/策略/分钟回测 + 财务快照因子(点时口径),T+1/费用/滑点约束,结果可导出 | [features.md](./docs/features.md) | | 🧪 **回测研究** | 因子/策略/分钟回测 + 财务快照因子(点时口径),T+1/费用/滑点约束,结果可导出 | [features.md](./docs/features.md) |
| ⛏️ **因子挖掘** | 嵌套样本外搜索多因子排名组合,与自有策略对照,候选库显式发布、永不自动上线 | [mining.md](./docs/mining.md) | | ⛏️ **因子挖掘** | 嵌套样本外搜索多因子排名组合,与自有策略对照,候选库显式发布、永不自动上线 | [mining.md](./docs/mining.md) |
@@ -335,7 +335,7 @@ PORT=3018 # 服务端口
| [docs/configuration.md](./docs/configuration.md) | 所有 `.env` 配置项详解(数据源、AI、服务、密码、数据目录) | | [docs/configuration.md](./docs/configuration.md) | 所有 `.env` 配置项详解(数据源、AI、服务、密码、数据目录) |
| [docs/features.md](./docs/features.md) | 各功能模块详细说明(选股/指标/回测/监控/个股分析/数据扩展) | | [docs/features.md](./docs/features.md) | 各功能模块详细说明(选股/指标/回测/监控/个股分析/数据扩展) |
| [docs/custom-data-source.md](./docs/custom-data-source.md) | 自定义数据源接入、能力路由契约、YAML 配置与 mock 联调示例 | | [docs/custom-data-source.md](./docs/custom-data-source.md) | 自定义数据源接入、能力路由契约、YAML 配置与 mock 联调示例 |
| [docs/strategy.md](./docs/strategy.md) | 策略体系(18 内置策略 + 三种扩展方式 + 文件结构) | | [docs/strategy.md](./docs/strategy.md) | 策略体系(25 内置策略 + 三种扩展方式 + 文件结构) |
| [docs/mining.md](./docs/mining.md) | 因子与策略挖掘口径、防泄漏、任务隔离和发布边界 | | [docs/mining.md](./docs/mining.md) | 因子与策略挖掘口径、防泄漏、任务隔离和发布边界 |
| [docs/market-phase.md](./docs/market-phase.md) | 市场情绪周期 6 阶段与概念/行业主线识别的口径与设计 | | [docs/market-phase.md](./docs/market-phase.md) | 市场情绪周期 6 阶段与概念/行业主线识别的口径与设计 |
| [docs/plugin-development.md](./docs/plugin-development.md) | 数据源插件开发规范(以 stock-sdk / fuyao 为参考实现) | | [docs/plugin-development.md](./docs/plugin-development.md) | 数据源插件开发规范(以 stock-sdk / fuyao 为参考实现) |
@@ -0,0 +1,88 @@
"""涨停基因活跃股 — 近期多次涨停的活跃标的池 (股性筛选, 非追板)"""
import numpy as np
from app.backtest.matrix import (
MarketDataMatrix,
SignalMatrix,
make_signal_matrix,
matrix_feature,
)
META = {
"id": "active_limit_gene",
"name": "涨停基因活跃股",
"description": "近60日涨停次数达标的活跃标的, 且当日未涨停、缩量休整 — 股性筛选池",
"tags": ["涨停", "活跃", "股性"],
"asset_types": ["stock"],
"timeframes": ["1d"],
"params": [
{
"id": "min_limit_count",
"label": "近60日最少涨停次数",
"type": "int",
"default": 3,
"min": 2,
"max": 10,
"step": 1,
},
{
"id": "vol_ratio_max",
"label": "当日量比上限 (休整)",
"type": "float",
"default": 1.2,
"min": 0.5,
"max": 3.0,
"step": 0.1,
},
{
"id": "max_change_pct",
"label": "当日涨幅上限% (排除涨停)",
"type": "float",
"default": 7.0,
"min": 3.0,
"max": 15.0,
"step": 0.5,
},
],
"scoring": {"limit_up_count_60d": 0.4, "momentum_20d": 0.3, "turnover_ratio_5d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
EXECUTION_BACKEND = "matrix_native"
ENTRY_SIGNALS = ["signal_active_limit_gene"]
EXIT_SIGNALS = ["signal_active_gene_cool"]
STOP_LOSS = -0.08
MAX_HOLD_DAYS = 25
class ActiveLimitGeneMatrixStrategy:
def required_fields(self) -> frozenset[str]:
return frozenset({"close", "volume"})
def required_warmup_bars(self, params: dict) -> int:
del params
return 70
def compute_signals(self, market: MarketDataMatrix, params: dict) -> SignalMatrix:
entry = matrix_feature(market, "limit_up_count_60d") >= int(params.get("min_limit_count", 3))
# 当日未涨停 (休整日而非情绪顶点) 且量能收敛
entry &= matrix_feature(market, "change_pct") < float(params.get("max_change_pct", 7.0)) / 100.0
entry &= matrix_feature(market, "vol_ratio_5d") <= float(params.get("vol_ratio_max", 1.2))
# 出场: 股性冷却 (60日涨停计数回落到阈值下)
exit_ = matrix_feature(market, "limit_up_count_60d") < int(params.get("min_limit_count", 3)) - 1
return make_signal_matrix(
market.shape,
entry=entry.astype(np.uint8),
exit=exit_.astype(np.uint8),
entry_signal_code=np.where(entry, 0, -1).astype(np.int16),
exit_signal_code=np.where(exit_, 0, -1).astype(np.int16),
entry_signal_ids=("signal_active_limit_gene",),
exit_signal_ids=("signal_active_gene_cool",),
)
MATRIX_STRATEGY = ActiveLimitGeneMatrixStrategy()
@@ -0,0 +1,83 @@
"""放量创60日新高 — 突破前期高点平台 + 量能确认"""
import numpy as np
from app.backtest.matrix import (
MarketDataMatrix,
SignalMatrix,
make_signal_matrix,
matrix_feature,
)
from app.backtest.matrix import (
valid_shift as shift,
)
META = {
"id": "breakout_new_high_60d",
"name": "放量创60日新高",
"description": "收盘突破前60日最高价, 量比配合, 趋势强度确认",
"tags": ["突破", "新高"],
"asset_types": ["stock", "etf"],
"timeframes": ["1d"],
"params": [
{"id": "require_volume", "label": "要求量比配合", "type": "bool", "default": True},
{
"id": "vol_ratio_min",
"label": "最低量比",
"type": "float",
"default": 1.3,
"min": 0.5,
"max": 5.0,
"step": 0.1,
},
{
"id": "min_change_pct",
"label": "最低当日涨幅%",
"type": "float",
"default": 2.0,
"min": 0.0,
"max": 10.0,
"step": 0.5,
},
],
"scoring": {"momentum_20d": 0.4, "vol_ratio_5d": 0.3, "change_pct": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
EXECUTION_BACKEND = "matrix_native"
ENTRY_SIGNALS = ["signal_breakout_high_60d"]
EXIT_SIGNALS = ["signal_break_ma20_lose"]
STOP_LOSS = -0.07
MAX_HOLD_DAYS = 20
class BreakoutNewHigh60dMatrixStrategy:
def required_fields(self) -> frozenset[str]:
return frozenset({"close", "volume"})
def required_warmup_bars(self, params: dict) -> int:
del params
return 70
def compute_signals(self, market: MarketDataMatrix, params: dict) -> SignalMatrix:
prior_high = shift(matrix_feature(market, "high_60d"), 1) # 昨日及以前的60日高点
entry = market.close > prior_high
entry &= matrix_feature(market, "change_pct") >= float(params.get("min_change_pct", 2.0)) / 100.0
if params.get("require_volume", True):
entry &= matrix_feature(market, "vol_ratio_5d") >= float(params.get("vol_ratio_min", 1.3))
ma20 = matrix_feature(market, "ma20")
exit_ = market.close < ma20
return make_signal_matrix(
market.shape,
entry=entry.astype(np.uint8),
exit=exit_.astype(np.uint8),
entry_signal_code=np.where(entry, 0, -1).astype(np.int16),
exit_signal_code=np.where(exit_, 0, -1).astype(np.int16),
entry_signal_ids=("signal_breakout_high_60d",),
exit_signal_ids=("signal_break_ma20_lose",),
)
MATRIX_STRATEGY = BreakoutNewHigh60dMatrixStrategy()
@@ -0,0 +1,90 @@
"""长下影反击 — 超跌后放量收长下影线, 多头承接确认"""
import numpy as np
from app.backtest.matrix import (
MarketDataMatrix,
SignalMatrix,
make_signal_matrix,
matrix_feature,
)
from app.backtest.matrix import (
valid_shift as shift,
)
META = {
"id": "long_lower_shadow_reversal",
"name": "长下影反击",
"description": "近5日超跌后收长下影线且收盘收复实体, 下方承接强势的反转信号",
"tags": ["K线形态", "超跌", "反转"],
"asset_types": ["stock"],
"timeframes": ["1d"],
"params": [
{
"id": "shadow_pct_min",
"label": "下影线最低长度% (相对昨收)",
"type": "float",
"default": 3.0,
"min": 1.0,
"max": 10.0,
"step": 0.5,
},
{
"id": "drop_pct_max",
"label": "近5日累计跌幅下限% (负值)",
"type": "float",
"default": -5.0,
"min": -30.0,
"max": 0.0,
"step": 1.0,
},
{"id": "require_recovery", "label": "要求收盘收复上半区", "type": "bool", "default": True},
],
"scoring": {"momentum_20d": 0.3, "vol_ratio_5d": 0.4, "change_pct": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
EXECUTION_BACKEND = "matrix_native"
ENTRY_SIGNALS = ["signal_long_lower_shadow"]
EXIT_SIGNALS = ["signal_ma5_lose_after_shadow"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 10
class LongLowerShadowReversalMatrixStrategy:
def required_fields(self) -> frozenset[str]:
return frozenset({"open", "high", "low", "close", "volume"})
def required_warmup_bars(self, params: dict) -> int:
del params
return 20
def compute_signals(self, market: MarketDataMatrix, params: dict) -> SignalMatrix:
prev_close = matrix_feature(market, "prev_close")
body_bot = np.minimum(market.open, market.close)
shadow_pct = (body_bot - market.low) / prev_close * 100.0
entry = shadow_pct >= float(params.get("shadow_pct_min", 3.0))
entry &= matrix_feature(market, "momentum_5d") <= float(params.get("drop_pct_max", -5.0)) / 100.0
entry &= matrix_feature(market, "vol_ratio_5d") >= 1.2 # 承接需有量
if params.get("require_recovery", True):
# 收盘位于当日振幅上半部 (close_position ∈ [0,1]): 长下影反击常为低开回拉,
# 收盘仍可能低于开盘 (假阴线), 用位置而非阴阳判定收复力度
entry &= matrix_feature(market, "close_position") >= 0.5
ma5 = matrix_feature(market, "ma5")
exit_ = market.close < ma5
return make_signal_matrix(
market.shape,
entry=entry.astype(np.uint8),
exit=exit_.astype(np.uint8),
entry_signal_code=np.where(entry, 0, -1).astype(np.int16),
exit_signal_code=np.where(exit_, 0, -1).astype(np.int16),
entry_signal_ids=("signal_long_lower_shadow",),
exit_signal_ids=("signal_ma5_lose_after_shadow",),
)
MATRIX_STRATEGY = LongLowerShadowReversalMatrixStrategy()
@@ -0,0 +1,105 @@
"""均线粘合突破 — MA5/10/20 挤压收敛后放量向上突破 (变盘启动点)"""
import numpy as np
from app.backtest.matrix import (
MarketDataMatrix,
SignalMatrix,
make_signal_matrix,
matrix_feature,
)
from app.backtest.matrix import (
valid_shift as shift,
)
META = {
"id": "ma_convergence_breakout",
"name": "均线粘合突破",
"description": "MA5/10/20 粘合收敛后放量突破, 挤压释放的变盘启动点",
"tags": ["均线", "粘合", "突破"],
"asset_types": ["stock", "etf"],
"timeframes": ["1d"],
"params": [
{
"id": "spread_pct_max",
"label": "粘合带宽上限% (三线极差/MA20)",
"type": "float",
"default": 2.5,
"min": 0.5,
"max": 8.0,
"step": 0.5,
},
{
"id": "squeeze_days",
"label": "粘合持续天数",
"type": "int",
"default": 5,
"min": 3,
"max": 15,
"step": 1,
},
{
"id": "min_change_pct",
"label": "突破日最低涨幅%",
"type": "float",
"default": 2.0,
"min": 0.0,
"max": 10.0,
"step": 0.5,
},
{"id": "require_volume", "label": "要求量比配合", "type": "bool", "default": True},
],
"scoring": {"vol_ratio_5d": 0.4, "change_pct": 0.3, "momentum_20d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
EXECUTION_BACKEND = "matrix_native"
ENTRY_SIGNALS = ["signal_ma_convergence_breakout"]
EXIT_SIGNALS = ["signal_ma10_lose"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 15
class MAConvergenceBreakoutMatrixStrategy:
def required_fields(self) -> frozenset[str]:
return frozenset({"close", "volume"})
def required_warmup_bars(self, params: dict) -> int:
return 20 + int(params.get("squeeze_days", 5))
def compute_signals(self, market: MarketDataMatrix, params: dict) -> SignalMatrix:
ma5 = matrix_feature(market, "ma5")
ma10 = matrix_feature(market, "ma10")
ma20 = matrix_feature(market, "ma20")
band_top = np.maximum(np.maximum(ma5, ma10), ma20)
band_bot = np.minimum(np.minimum(ma5, ma10), ma20)
spread = np.abs(band_top - band_bot) / ma20 * 100.0
# 粘合: 近 squeeze_days 日 (含昨日, 不含今日) 带宽持续低于阈值
days = max(2, int(params.get("squeeze_days", 5)))
tight = spread <= float(params.get("spread_pct_max", 2.5))
squeeze = shift(tight, 1).astype(bool)
for k in range(2, days + 1):
squeeze &= shift(tight, k).astype(bool)
# 今日变盘: 放量阳线脱离粘合带, 收盘站上三线上方
entry = squeeze & (market.close > band_top)
entry &= matrix_feature(market, "change_pct") >= float(params.get("min_change_pct", 2.0)) / 100.0
if params.get("require_volume", True):
entry &= matrix_feature(market, "vol_ratio_5d") >= 1.2
exit_ = market.close < ma10
return make_signal_matrix(
market.shape,
entry=entry.astype(np.uint8),
exit=exit_.astype(np.uint8),
entry_signal_code=np.where(entry, 0, -1).astype(np.int16),
exit_signal_code=np.where(exit_, 0, -1).astype(np.int16),
entry_signal_ids=("signal_ma_convergence_breakout",),
exit_signal_ids=("signal_ma10_lose",),
)
MATRIX_STRATEGY = MAConvergenceBreakoutMatrixStrategy()
@@ -0,0 +1,89 @@
"""MACD 零下回升 — 股价新低而 DIF 拒绝新低 (底背离简化形态)"""
import numpy as np
from app.backtest.matrix import (
MarketDataMatrix,
SignalMatrix,
make_signal_matrix,
matrix_feature,
)
from app.backtest.matrix import (
valid_shift as shift,
)
META = {
"id": "macd_below_zero_revival",
"name": "MACD 零下回升",
"description": "股价创阶段新低而 MACD DIF 拒绝新低并回升 (底背离简化), 零轴下方动能修复",
"tags": ["MACD", "背离", "超跌"],
"asset_types": ["stock"],
"timeframes": ["1d"],
"params": [
{
"id": "low_window",
"label": "新低回看天数",
"type": "int",
"default": 20,
"min": 10,
"max": 60,
"step": 5,
},
{
"id": "revive_days",
"label": "DIF 回升对比天数",
"type": "int",
"default": 10,
"min": 5,
"max": 20,
"step": 1,
},
],
"scoring": {"momentum_20d": 0.4, "change_pct": 0.3, "vol_ratio_5d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
EXECUTION_BACKEND = "matrix_native"
ENTRY_SIGNALS = ["signal_macd_below_zero_revival"]
EXIT_SIGNALS = ["signal_macd_golden_above_zero"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 20
class MACDBelowZeroRevivalMatrixStrategy:
def required_fields(self) -> frozenset[str]:
return frozenset({"close", "volume"})
def required_warmup_bars(self, params: dict) -> int:
return int(params.get("low_window", 20)) + 40
def compute_signals(self, market: MarketDataMatrix, params: dict) -> SignalMatrix:
win = max(5, int(params.get("low_window", 20)))
revive = max(3, int(params.get("revive_days", 10)))
dif = matrix_feature(market, "macd_dif")
# 阶段新低: 今日收盘 <= 前 win 日 (不含今日) 的最低收盘
prior_min = shift(market.close, 1)
for k in range(2, win + 1):
prior_min = np.fmin(prior_min, shift(market.close, k))
new_low = market.close <= prior_min
# 零下 + DIF 较 revive 日前抬升 (动能拒绝新低)
entry = new_low & (dif < 0) & (dif > shift(dif, revive))
# 出场: DIF 上穿零轴 (修复完成)
exit_ = (dif > 0) & (shift(dif, 1) <= 0)
return make_signal_matrix(
market.shape,
entry=entry.astype(np.uint8),
exit=exit_.astype(np.uint8),
entry_signal_code=np.where(entry, 0, -1).astype(np.int16),
exit_signal_code=np.where(exit_, 0, -1).astype(np.int16),
entry_signal_ids=("signal_macd_below_zero_revival",),
exit_signal_ids=("signal_macd_golden_above_zero",),
)
MATRIX_STRATEGY = MACDBelowZeroRevivalMatrixStrategy()
@@ -0,0 +1,99 @@
"""平台缩量整理突破 — 窄幅横盘蓄势后放量突破平台上沿"""
import numpy as np
from app.backtest.matrix import (
MarketDataMatrix,
SignalMatrix,
make_signal_matrix,
matrix_feature,
)
from app.backtest.matrix import (
valid_shift as shift,
)
META = {
"id": "platform_consolidation_breakout",
"name": "平台整理突破",
"description": "近N日窄幅横盘 (振幅收敛) 后放量突破平台上沿, 蓄势变盘",
"tags": ["形态", "平台", "突破"],
"asset_types": ["stock"],
"timeframes": ["1d"],
"params": [
{
"id": "platform_days",
"label": "平台天数",
"type": "int",
"default": 10,
"min": 5,
"max": 30,
"step": 1,
},
{
"id": "range_pct_max",
"label": "平台振幅上限%",
"type": "float",
"default": 8.0,
"min": 3.0,
"max": 20.0,
"step": 0.5,
},
{
"id": "vol_ratio_min",
"label": "突破日最低量比",
"type": "float",
"default": 1.5,
"min": 1.0,
"max": 5.0,
"step": 0.1,
},
],
"scoring": {"vol_ratio_5d": 0.4, "momentum_20d": 0.3, "change_pct": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
EXECUTION_BACKEND = "matrix_native"
ENTRY_SIGNALS = ["signal_platform_breakout"]
EXIT_SIGNALS = ["signal_platform_fail_ma20"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 15
class PlatformConsolidationBreakoutMatrixStrategy:
def required_fields(self) -> frozenset[str]:
return frozenset({"high", "low", "close", "volume"})
def required_warmup_bars(self, params: dict) -> int:
return int(params.get("platform_days", 10)) + 10
def compute_signals(self, market: MarketDataMatrix, params: dict) -> SignalMatrix:
days = max(4, int(params.get("platform_days", 10)))
# 平台区间: 前 days 日 (不含今日) 的最高/最低 (滚动窗口平移)
prior_high = shift(market.high, 1)
prior_low = shift(market.low, 1)
for k in range(2, days + 1):
prior_high = np.fmax(prior_high, shift(market.high, k))
prior_low = np.fmin(prior_low, shift(market.low, k))
range_pct = (prior_high - prior_low) / market.close * 100.0
# 平台成立 + 今日放量突破平台上沿
entry = range_pct <= float(params.get("range_pct_max", 8.0))
entry &= market.close > prior_high
entry &= matrix_feature(market, "vol_ratio_5d") >= float(params.get("vol_ratio_min", 1.5))
exit_ = market.close < matrix_feature(market, "ma20")
return make_signal_matrix(
market.shape,
entry=entry.astype(np.uint8),
exit=exit_.astype(np.uint8),
entry_signal_code=np.where(entry, 0, -1).astype(np.int16),
exit_signal_code=np.where(exit_, 0, -1).astype(np.int16),
entry_signal_ids=("signal_platform_breakout",),
exit_signal_ids=("signal_platform_fail_ma20",),
)
MATRIX_STRATEGY = PlatformConsolidationBreakoutMatrixStrategy()
@@ -0,0 +1,98 @@
"""RSI 中轴回踩 — 强趋势中 RSI 回落至 50 中轴附近而不破, 低吸点"""
import numpy as np
from app.backtest.matrix import (
MarketDataMatrix,
SignalMatrix,
make_signal_matrix,
matrix_feature,
)
from app.backtest.matrix import (
valid_shift as shift,
)
META = {
"id": "rsi_midline_pullback",
"name": "RSI 中轴回踩",
"description": "多头趋势 (MA60 上方) 中 RSI 回落至 50 中轴区间企稳, 强势股低吸",
"tags": ["RSI", "趋势", "低吸"],
"asset_types": ["stock", "etf"],
"timeframes": ["1d"],
"params": [
{
"id": "rsi_period",
"label": "RSI 周期",
"type": "int",
"default": 14,
"min": 6,
"max": 24,
"step": 2,
},
{
"id": "mid_low",
"label": "中轴区间下沿",
"type": "float",
"default": 45.0,
"min": 30.0,
"max": 55.0,
"step": 1.0,
},
{
"id": "mid_high",
"label": "中轴区间上沿",
"type": "float",
"default": 60.0,
"min": 45.0,
"max": 70.0,
"step": 1.0,
},
],
"scoring": {"momentum_20d": 0.4, "up_days_20d": 0.3, "vol_ratio_5d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
EXECUTION_BACKEND = "matrix_native"
ENTRY_SIGNALS = ["signal_rsi_midline_pullback"]
EXIT_SIGNALS = ["signal_rsi_midline_fail"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 15
class RSIMidlinePullbackMatrixStrategy:
def required_fields(self) -> frozenset[str]:
return frozenset({"close", "volume"})
def required_warmup_bars(self, params: dict) -> int:
return int(params.get("rsi_period", 14)) + 60
def compute_signals(self, market: MarketDataMatrix, params: dict) -> SignalMatrix:
rsi = matrix_feature(market, f"rsi_{int(params.get('rsi_period', 14))}")
lo = float(params.get("mid_low", 45.0))
hi = float(params.get("mid_high", 60.0))
# 趋势前提: MA60 上方; 今日 RSI 落在中轴区间
entry = market.close > matrix_feature(market, "ma60")
entry &= (rsi >= lo) & (rsi <= hi)
# 回踩而非走坏: 近5日内出现过 RSI > hi+5 (强势记忆), 且今日未破中轴下沿
was_strong = shift(rsi, 1) > hi + 5
for k in range(2, 6):
was_strong |= shift(rsi, k) > hi + 5
entry &= was_strong
exit_ = market.close < matrix_feature(market, "ma20")
return make_signal_matrix(
market.shape,
entry=entry.astype(np.uint8),
exit=exit_.astype(np.uint8),
entry_signal_code=np.where(entry, 0, -1).astype(np.int16),
exit_signal_code=np.where(exit_, 0, -1).astype(np.int16),
entry_signal_ids=("signal_rsi_midline_pullback",),
exit_signal_ids=("signal_rsi_midline_fail",),
)
MATRIX_STRATEGY = RSIMidlinePullbackMatrixStrategy()
@@ -320,7 +320,7 @@ def test_builtin_matrix_strategies_use_their_declared_formula_modules():
) )
# 分钟形态策略 (minute_red_streak) 已迁至自定义策略目录, 内置策略全部 matrix 后端 # 分钟形态策略 (minute_red_streak) 已迁至自定义策略目录, 内置策略全部 matrix 后端
assert len(strategy_files) == 19 assert len(strategy_files) == 26
for strategy_path in strategy_files: for strategy_path in strategy_files:
strategy = StrategyEngine._load_file(strategy_path) strategy = StrategyEngine._load_file(strategy_path)
assert strategy.execution_backend == "matrix_native" assert strategy.execution_backend == "matrix_native"
@@ -790,7 +790,7 @@ def test_registered_builtin_matrix_strategies_share_one_cache_profile():
if s.execution_backend != "minute_filter" if s.execution_backend != "minute_filter"
) )
assert len(strategies) == 19 assert len(strategies) == 26
assert all(strategy.execution_backend == "matrix_native" for strategy in strategies) assert all(strategy.execution_backend == "matrix_native" for strategy in strategies)
assert profile.warmup_bars > 0 assert profile.warmup_bars > 0
assert profile.forward_bars == max(int(strategy.max_hold_days or 0) for strategy in strategies) assert profile.forward_bars == max(int(strategy.max_hold_days or 0) for strategy in strategies)
+4 -4
View File
@@ -8,13 +8,13 @@
## 内置策略 ## 内置策略
**18 个内置策略**,每个策略一个独立 Python 文件,基于 Polars 表达式向量化实现(`backend/app/strategy/builtin/`): **25 个内置策略**,每个策略一个独立 Python 文件,基于 Polars 表达式向量化实现(`backend/app/strategy/builtin/`):
| 类型 | 代表策略 | | 类型 | 代表策略 |
| :---------- | :------------------------------------------------------- | | :---------- | :------------------------------------------------------- |
| 趋势 / 形态 | 趋势突破 · 均线多头 · MA 金叉 · MACD 金叉放量 · 布林突破 | | 趋势 / 形态 | 趋势突破 · 均线多头 · MA 金叉 · MACD 金叉放量 · 布林突破 · 均线粘合突破 · 平台整理突破 · 放量创60日新高 |
| 量价 / 涨停 | 量价齐升 · 高换手强势 · 连板股 · 断板反包 · 涨停动量 · 接近涨停 | | 量价 / 涨停 | 量价齐升 · 高换手强势 · 连板股 · 断板反包 · 涨停动量 · 接近涨停 · 涨停基因活跃股 |
| 反转 / 波动 | 超跌反弹 · 超卖反转 · 新低反转 · 低波动龙头 · 回踩 MA20 · 回踩支撑 · 强势开盘 | | 反转 / 波动 | 超跌反弹 · 超卖反转 · 新低反转 · 低波动龙头 · 回踩 MA20 · 回踩支撑 · 强势开盘 · MACD 零下回升 · 长下影反击 · RSI 中轴回踩 |
内置目录 `backend/app/strategy/builtin/` 还包含一个仅供挖掘 worker 使用的受控因子排名研究模板。它不出现在普通选股列表,也不能通过普通策略 API 直接运行或保存 override;挖掘结果发布时会生成独立策略。详见 [因子与策略挖掘](./mining.md)。 内置目录 `backend/app/strategy/builtin/` 还包含一个仅供挖掘 worker 使用的受控因子排名研究模板。它不出现在普通选股列表,也不能通过普通策略 API 直接运行或保存 override;挖掘结果发布时会生成独立策略。详见 [因子与策略挖掘](./mining.md)。