复制 local 代码到 serve
This commit is contained in:
@@ -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"))
|
||||
)
|
||||
Reference in New Issue
Block a user