项目目录从 refer 迁移到 local

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-04 16:32:48 +08:00
parent 648a8b7f1c
commit aad34202f1
302 changed files with 0 additions and 0 deletions
@@ -0,0 +1,31 @@
"""布林突破 — 突破布林上轨 + 放量"""
import polars as pl
META = {
"id": "boll_breakout",
"name": "布林突破",
"description": "突破布林上轨 + 放量, 强势加速信号",
"tags": ["布林", "突破"],
"params": [
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
"default": 1.5, "min": 0.5, "max": 5.0, "step": 0.1},
],
"scoring": {"vol_ratio_5d": 0.4, "change_pct": 0.3, "momentum_20d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_boll_breakout_upper"]
EXIT_SIGNALS = ["signal_boll_breakdown_lower"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 15
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_min = params.get("vol_ratio_min", 1.5)
return (
pl.col("signal_boll_breakout_upper").fill_null(False)
& (pl.col("vol_ratio_5d") >= vol_min)
)
@@ -0,0 +1,35 @@
"""断板反包 — 涨停 + 放量 + 涨幅 >3%"""
import polars as pl
META = {
"id": "broken_board_recovery",
"name": "断板反包",
"description": "连板≥2后断板1-2天, 出现放量反包信号",
"tags": ["涨停", "反包"],
"params": [
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
"default": 1.5, "min": 0.5, "max": 5.0, "step": 0.1},
{"id": "change_pct_min", "label": "最低涨幅", "type": "float",
"default": 0.03, "min": 0.01, "max": 0.10, "step": 0.01},
],
"scoring": {"change_pct": 0.4, "vol_ratio_5d": 0.3, "momentum_5d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_limit_up"]
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 10
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_min = params.get("vol_ratio_min", 1.5)
chg_min = params.get("change_pct_min", 0.03)
return (
pl.col("signal_limit_up").fill_null(False)
& (pl.col("vol_ratio_5d") >= vol_min)
& (pl.col("change_pct") > chg_min)
)
@@ -0,0 +1,29 @@
"""均线多头 — MA5>MA10>MA20>MA60 + 短期动量为正"""
import polars as pl
META = {
"id": "bullish_alignment",
"name": "均线多头",
"description": "MA5>MA10>MA20>MA60多头排列 + 短期动量为正",
"tags": ["均线", "多头"],
"params": [],
"scoring": {"momentum_60d": 0.4, "momentum_20d": 0.3, "turnover_rate": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_ma_golden_5_20", "signal_ma_golden_20_60"]
EXIT_SIGNALS = ["signal_ma_dead_5_20", "signal_ma20_breakdown"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 20
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
return (
(pl.col("ma5") > pl.col("ma10"))
& (pl.col("ma10") > pl.col("ma20"))
& (pl.col("ma20") > pl.col("ma60"))
& (pl.col("momentum_20d") > 0)
)
@@ -0,0 +1,31 @@
"""连板股 — 涨停且连续涨停≥2天"""
import polars as pl
META = {
"id": "consecutive_limit_ups",
"name": "连板股",
"description": "当日涨停且连续涨停≥2天, 强势追涨",
"tags": ["涨停", "连板"],
"params": [
{"id": "min_boards", "label": "最少连板数", "type": "int",
"default": 2, "min": 1, "max": 20, "step": 1},
],
"scoring": {"consecutive_limit_ups": 0.5, "change_pct": 0.3, "amount": 0.2},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_limit_up"]
EXIT_SIGNALS = []
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 5
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
min_boards = params.get("min_boards", 2)
return (
pl.col("signal_limit_up").fill_null(False)
& (pl.col("consecutive_limit_ups") >= min_boards)
)
@@ -0,0 +1,34 @@
"""高换手拉升 — 换手率 > 5% 且涨幅 > 3%, 资金活跃"""
import polars as pl
META = {
"id": "high_turnover_surge",
"name": "高换手拉升",
"description": "换手率 > 5% 且涨幅 > 3%, 资金活跃",
"tags": ["换手率", "放量", "资金"],
"params": [
{"id": "min_turnover", "label": "最低换手率%", "type": "float",
"default": 5.0, "min": 1.0, "max": 20.0, "step": 0.5},
{"id": "min_change", "label": "最低涨幅%", "type": "float",
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
],
"scoring": {"turnover_rate": 0.4, "change_pct": 0.3, "momentum_5d": 0.3},
"order_by": "score",
"descending": True,
"limit": 50,
}
ENTRY_SIGNALS = ["signal_volume_surge"]
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 10
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
min_to = params.get("min_turnover", 5.0) / 100.0
min_chg = params.get("min_change", 3.0) / 100.0
return (
(pl.col("turnover_rate") > min_to)
& (pl.col("change_pct") > min_chg)
)
@@ -0,0 +1,34 @@
"""连板接力 — 近2日涨停且今日涨幅 > 5%, 连板股追踪"""
import polars as pl
META = {
"id": "limit_up_momentum",
"name": "连板接力",
"description": "连板股 + 今日涨幅 > 5%, 连板接力追踪",
"tags": ["涨停", "连板", "接力"],
"params": [
{"id": "min_change", "label": "最低涨幅%", "type": "float",
"default": 5.0, "min": 2.0, "max": 15.0, "step": 0.5},
{"id": "min_boards", "label": "最少连板", "type": "int",
"default": 1, "min": 1, "max": 10, "step": 1},
],
"scoring": {"consecutive_limit_ups": 0.4, "change_pct": 0.3, "amount": 0.3},
"order_by": "score",
"descending": True,
"limit": 50,
}
ENTRY_SIGNALS = ["signal_limit_up"]
EXIT_SIGNALS = []
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 5
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
min_chg = params.get("min_change", 5.0) / 100.0
min_boards = params.get("min_boards", 1)
return (
(pl.col("change_pct") > min_chg)
& (pl.col("consecutive_limit_ups") >= min_boards)
)
@@ -0,0 +1,32 @@
"""低波动龙头 — 正动量 + 低波动 + MA20上方"""
import polars as pl
META = {
"id": "low_volatility_leader",
"name": "低波动龙头",
"description": "20日动量为正 + 年化波动 < 30% + MA20上方",
"tags": ["低波动", "龙头"],
"params": [
{"id": "vol_max", "label": "最大年化波动", "type": "float",
"default": 0.30, "min": 0.05, "max": 1.0, "step": 0.01},
],
"scoring": {"momentum_60d": 0.4, "momentum_20d": 0.3, "turnover_rate": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_ma20_breakout"]
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 30
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_max = params.get("vol_max", 0.30)
return (
(pl.col("momentum_20d") > 0)
& (pl.col("annual_vol_20d") < vol_max)
& (pl.col("close") > pl.col("ma20"))
)
@@ -0,0 +1,32 @@
"""MA金叉 — MA5上穿MA20 + 量能配合 + MA60上方"""
import polars as pl
META = {
"id": "ma_golden_cross",
"name": "MA 金叉",
"description": "MA5上穿MA20当日触发, 量能配合",
"tags": ["均线", "金叉"],
"params": [
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
"default": 1.2, "min": 0.5, "max": 5.0, "step": 0.1},
],
"scoring": {"momentum_20d": 0.5, "vol_ratio_5d": 0.3, "change_pct": 0.2},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_ma_golden_5_20"]
EXIT_SIGNALS = ["signal_ma_dead_5_20"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 15
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_min = params.get("vol_ratio_min", 1.2)
return (
pl.col("signal_ma_golden_5_20").fill_null(False)
& (pl.col("vol_ratio_5d") >= vol_min)
& (pl.col("close") > pl.col("ma60"))
)
@@ -0,0 +1,31 @@
"""MACD金叉放量 — MACD金叉当日 + 量能放大"""
import polars as pl
META = {
"id": "macd_golden",
"name": "MACD 金叉放量",
"description": "MACD金叉当日 + 量能放大",
"tags": ["MACD", "金叉", "放量"],
"params": [
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
"default": 1.5, "min": 0.5, "max": 5.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_macd_golden"]
EXIT_SIGNALS = ["signal_macd_dead"]
STOP_LOSS = -0.07
MAX_HOLD_DAYS = 20
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_min = params.get("vol_ratio_min", 1.5)
return (
pl.col("signal_macd_golden").fill_null(False)
& (pl.col("vol_ratio_5d") >= vol_min)
)
@@ -0,0 +1,32 @@
"""新低反转 — 60日新低后收阳放量"""
import polars as pl
META = {
"id": "n_day_low_reversal",
"name": "新低反转",
"description": "触及60日新低后当日收阳放量, 反转信号",
"tags": ["反转", "新低"],
"params": [
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
"default": 1.5, "min": 0.5, "max": 5.0, "step": 0.1},
],
"scoring": {"change_pct": 0.4, "vol_ratio_5d": 0.3, "momentum_5d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_n_day_low"]
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 15
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_min = params.get("vol_ratio_min", 1.5)
return (
pl.col("signal_n_day_low").fill_null(False)
& (pl.col("close") > pl.col("open"))
& (pl.col("vol_ratio_5d") >= vol_min)
)
@@ -0,0 +1,55 @@
"""逼近涨停 — 涨幅 > 7% 且距涨停 < 3%, 盘后选股"""
import polars as pl
def _limit_pct() -> pl.Expr:
"""根据板块和 ST 动态计算涨跌幅限制 (小数)。
创业板(300/301)/科创板(688): 20%
北交所(.BJ): 30%
ST: 5%
主板: 10%
"""
is_st = pl.col("name").str.contains("(?i)ST").fill_null(False)
is_cyb = pl.col("symbol").str.starts_with("300") | pl.col("symbol").str.starts_with("301")
is_kcb = pl.col("symbol").str.starts_with("688")
is_bj = pl.col("symbol").str.contains(r"\.BJ$")
return (
pl.when(is_st).then(0.05)
.when(is_cyb | is_kcb).then(0.20)
.when(is_bj).then(0.30)
.otherwise(0.10)
)
META = {
"id": "near_limit_up",
"name": "逼近涨停",
"description": "涨幅 > 7% 且距涨停 < 3%, 追涨信号",
"tags": ["涨停", "追涨"],
"params": [
{"id": "min_change", "label": "最低涨幅%", "type": "float",
"default": 7.0, "min": 3.0, "max": 15.0, "step": 1.0},
{"id": "limit_gap", "label": "距涨停空间%", "type": "float",
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
],
"scoring": {"change_pct": 0.5, "amount": 0.3, "momentum_5d": 0.2},
"order_by": "score",
"descending": True,
"limit": 50,
}
ENTRY_SIGNALS = []
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 5
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
min_chg = params.get("min_change", 7.0) / 100.0
gap = params.get("limit_gap", 3.0) / 100.0
lp = _limit_pct()
return (
(pl.col("change_pct") > min_chg)
& (pl.col("change_pct") < lp - gap)
)
@@ -0,0 +1,37 @@
"""超跌反弹 — RSI14 < 30 + 收阳 + 放量"""
import polars as pl
META = {
"id": "oversold_bounce",
"name": "超跌反弹",
"description": "RSI14 < 30超卖区 + 当日收阳 + 放量, 抄底信号",
"tags": ["超跌", "反弹", "RSI"],
"params": [
{"id": "rsi_max", "label": "RSI上限", "type": "float",
"default": 30.0, "min": 10.0, "max": 50.0, "step": 1.0},
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
"default": 1.2, "min": 0.5, "max": 5.0, "step": 0.1},
],
"scoring": {"change_pct": 0.3, "vol_ratio_5d": 0.3, "momentum_5d": 0.2, "rsi_14": 0.2},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = []
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 15
ALERTS = [
{"field": "rsi_14", "op": "<", "value": 25, "message": "RSI极度超卖"},
]
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
rsi_max = params.get("rsi_max", 30.0)
vol_min = params.get("vol_ratio_min", 1.2)
return (
(pl.col("rsi_14") < rsi_max)
& (pl.col("close") > pl.col("open"))
& (pl.col("vol_ratio_5d") >= vol_min)
)
@@ -0,0 +1,37 @@
"""超跌反弹 — RSI14 < 30 + 涨幅 > 1% + 站上 MA5, 超卖反弹信号"""
import polars as pl
META = {
"id": "oversold_reversal",
"name": "超跌反转",
"description": "RSI14 < 30超卖 + 涨幅 > 1% + 站上MA5, 超卖反转信号",
"tags": ["超跌", "反弹", "RSI"],
"params": [
{"id": "rsi_max", "label": "RSI上限", "type": "float",
"default": 30.0, "min": 10.0, "max": 50.0, "step": 1.0},
{"id": "min_change", "label": "最低涨幅%", "type": "float",
"default": 1.0, "min": 0.5, "max": 5.0, "step": 0.5},
],
"scoring": {"change_pct": 0.4, "rsi_14": 0.3, "vol_ratio_5d": 0.3},
"order_by": "score",
"descending": True,
"limit": 50,
}
ENTRY_SIGNALS = []
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 15
ALERTS = [
{"field": "rsi_14", "op": "<", "value": 25, "message": "RSI极度超卖"},
]
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
rsi_max = params.get("rsi_max", 30.0)
min_chg = params.get("min_change", 1.0) / 100.0
return (
(pl.col("rsi_14") < rsi_max)
& (pl.col("change_pct") > min_chg)
& (pl.col("close") > pl.col("ma5"))
)
@@ -0,0 +1,34 @@
"""均线回踩反弹 — 价格在 MA20 附近(±2%)且 MA 多头排列, 回踩买入"""
import polars as pl
META = {
"id": "pullback_ma20_bounce",
"name": "均线回踩反弹",
"description": "价格在MA20附近(±2%)且MA5>MA20>MA60多头排列, 回踩买入",
"tags": ["回踩", "均线", "反弹"],
"params": [
{"id": "ma_proximity", "label": "MA偏离度%", "type": "float",
"default": 2.0, "min": 0.5, "max": 5.0, "step": 0.5},
],
"scoring": {"momentum_60d": 0.4, "change_pct": 0.3, "momentum_20d": 0.3},
"order_by": "score",
"descending": True,
"limit": 50,
}
ENTRY_SIGNALS = ["signal_ma_golden_5_20"]
EXIT_SIGNALS = ["signal_ma20_breakdown", "signal_ma_dead_5_20"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 15
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
proximity = params.get("ma_proximity", 2.0) / 100.0
return (
(pl.col("close") > pl.col("ma20") * (1 - proximity))
& (pl.col("close") < pl.col("ma20") * (1 + proximity))
& (pl.col("ma5") > pl.col("ma20"))
& (pl.col("ma20") > pl.col("ma60"))
& (pl.col("change_pct") > 0)
)
@@ -0,0 +1,37 @@
"""缩量回踩 — 回踩MA20附近 + 缩量 + 中期趋势向上"""
import polars as pl
META = {
"id": "pullback_to_support",
"name": "缩量回踩",
"description": "回踩MA20附近 + 缩量 + 中期趋势向上",
"tags": ["回踩", "支撑"],
"params": [
{"id": "ma_proximity", "label": "均线偏离度", "type": "float",
"default": 0.02, "min": 0.01, "max": 0.05, "step": 0.005},
{"id": "vol_ratio_max", "label": "最大量比", "type": "float",
"default": 0.8, "min": 0.2, "max": 1.5, "step": 0.1},
],
"scoring": {"momentum_60d": 0.4, "momentum_20d": 0.3, "turnover_rate": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_ma_golden_5_20"]
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 20
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
proximity = params.get("ma_proximity", 0.02)
vol_max = params.get("vol_ratio_max", 0.8)
return (
(pl.col("close") > pl.col("ma20") * (1 - proximity))
& (pl.col("close") < pl.col("ma20") * (1 + proximity))
& (pl.col("vol_ratio_5d") < vol_max)
& (pl.col("close") > pl.col("ma60"))
& (pl.col("momentum_20d") > 0)
)
@@ -0,0 +1,35 @@
"""强势高开 — 高开 > 3% 且保持上涨, 集合竞价强势"""
import polars as pl
META = {
"id": "strong_open",
"name": "强势高开",
"description": "高开 > 3% 且收盘高于开盘价, 集合竞价强势",
"tags": ["高开", "强势"],
"params": [
{"id": "min_open_gap", "label": "最低高开%", "type": "float",
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
{"id": "min_change", "label": "最低涨幅%", "type": "float",
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
],
"scoring": {"change_pct": 0.4, "amplitude": 0.2, "amount": 0.4},
"order_by": "score",
"descending": True,
"limit": 50,
}
ENTRY_SIGNALS = []
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.05
MAX_HOLD_DAYS = 10
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
min_gap = params.get("min_open_gap", 3.0) / 100.0
min_chg = params.get("min_change", 3.0) / 100.0
return (
(pl.col("open") > pl.col("prev_close") * (1 + min_gap))
& (pl.col("close") > pl.col("open"))
& (pl.col("change_pct") > min_chg)
)
@@ -0,0 +1,42 @@
"""趋势突破 — MA60上方 + 60日新高 + 放量"""
import polars as pl
META = {
"id": "trend_breakout",
"name": "趋势突破",
"description": "MA60上方 + 60日新高 + 量能 ≥ 2倍均量",
"tags": ["趋势", "突破", "放量"],
"basic_filter": {
"price_min": 5,
"price_max": 200,
"market_cap_min": 20e8,
"amount_min": 1e8,
"exclude_st": True,
"exclude_new_days": 60,
},
"params": [
{"id": "vol_ratio_min", "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_n_day_high"]
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.08
MAX_HOLD_DAYS = 20
ALERTS = [
{"field": "signal_volume_surge", "message": "放量异动"},
]
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_min = params.get("vol_ratio_min", 2.0)
return (
(pl.col("close") > pl.col("ma60"))
& pl.col("signal_n_day_high").fill_null(False)
& (pl.col("vol_ratio_5d") >= vol_min)
)
@@ -0,0 +1,32 @@
"""量价齐升 — 突破MA20 + 放量 + 收阳"""
import polars as pl
META = {
"id": "volume_price_surge",
"name": "量价齐升",
"description": "突破MA20 + 放量 + 收阳",
"tags": ["量价", "突破"],
"params": [
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
"default": 2.0, "min": 0.5, "max": 10.0, "step": 0.1},
],
"scoring": {"vol_ratio_5d": 0.4, "change_pct": 0.3, "momentum_20d": 0.3},
"order_by": "score",
"descending": True,
"limit": 100,
}
ENTRY_SIGNALS = ["signal_ma20_breakout"]
EXIT_SIGNALS = ["signal_ma20_breakdown"]
STOP_LOSS = -0.06
MAX_HOLD_DAYS = 15
ALERTS = []
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
vol_min = params.get("vol_ratio_min", 2.0)
return (
pl.col("signal_ma20_breakout").fill_null(False)
& (pl.col("vol_ratio_5d") >= vol_min)
& (pl.col("close") > pl.col("open"))
)