mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 14:24:15 +08:00
feat: make builtin strategy filters configurable
This commit is contained in:
@@ -7,6 +7,10 @@ META = {
|
||||
"description": "突破布林上轨 + 放量, 强势加速信号",
|
||||
"tags": ["布林", "突破"],
|
||||
"params": [
|
||||
{"id": "require_boll_breakout", "label": "要求突破布林上轨", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 1.5, "min": 0.5, "max": 5.0, "step": 0.1},
|
||||
],
|
||||
@@ -25,7 +29,9 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_boll_breakout", True):
|
||||
expr = expr & pl.col("signal_boll_breakout_upper").fill_null(False)
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,14 @@ META = {
|
||||
"description": "连板≥2后断板1-2天, 出现放量反包信号",
|
||||
"tags": ["涨停", "反包"],
|
||||
"params": [
|
||||
{"id": "require_limit_up", "label": "要求当日涨停", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 1.5, "min": 0.5, "max": 5.0, "step": 0.1},
|
||||
{"id": "use_change_filter", "label": "启用涨幅过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "change_pct_min", "label": "最低涨幅", "type": "float",
|
||||
"default": 0.03, "min": 0.01, "max": 0.10, "step": 0.01},
|
||||
],
|
||||
@@ -28,8 +34,11 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_limit_up", True):
|
||||
expr = expr & pl.col("signal_limit_up").fill_null(False)
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
if params.get("use_change_filter", True):
|
||||
expr = expr & (pl.col("change_pct") > chg_min)
|
||||
return expr
|
||||
|
||||
@@ -6,7 +6,12 @@ META = {
|
||||
"name": "均线多头",
|
||||
"description": "MA5>MA10>MA20>MA60多头排列 + 短期动量为正",
|
||||
"tags": ["均线", "多头"],
|
||||
"params": [],
|
||||
"params": [
|
||||
{"id": "require_ma_alignment", "label": "要求均线多头排列", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "require_positive_momentum", "label": "要求20日动量为正", "type": "bool",
|
||||
"default": True},
|
||||
],
|
||||
"scoring": {"momentum_60d": 0.4, "momentum_20d": 0.3, "turnover_rate": 0.3},
|
||||
"order_by": "score",
|
||||
"descending": True,
|
||||
@@ -21,9 +26,14 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_ma_alignment", True):
|
||||
expr = (
|
||||
expr
|
||||
& (pl.col("ma5") > pl.col("ma10"))
|
||||
& (pl.col("ma10") > pl.col("ma20"))
|
||||
& (pl.col("ma20") > pl.col("ma60"))
|
||||
)
|
||||
if params.get("require_positive_momentum", True):
|
||||
expr = expr & (pl.col("momentum_20d") > 0)
|
||||
return expr
|
||||
|
||||
@@ -7,6 +7,10 @@ META = {
|
||||
"description": "当日涨停且连续涨停≥2天, 强势追涨",
|
||||
"tags": ["涨停", "连板"],
|
||||
"params": [
|
||||
{"id": "require_limit_up", "label": "要求当日涨停", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_boards_filter", "label": "启用连板数过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_boards", "label": "最少连板数", "type": "int",
|
||||
"default": 2, "min": 1, "max": 20, "step": 1},
|
||||
],
|
||||
@@ -25,7 +29,9 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_limit_up", True):
|
||||
expr = expr & pl.col("signal_limit_up").fill_null(False)
|
||||
if params.get("use_boards_filter", True):
|
||||
expr = expr & (pl.col("consecutive_limit_ups") >= min_boards)
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,12 @@ META = {
|
||||
"description": "换手率 > 5% 且涨幅 > 3%, 资金活跃",
|
||||
"tags": ["换手率", "放量", "资金"],
|
||||
"params": [
|
||||
{"id": "use_turnover_filter", "label": "启用换手率过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_turnover", "label": "最低换手率%", "type": "float",
|
||||
"default": 5.0, "min": 1.0, "max": 20.0, "step": 0.5},
|
||||
{"id": "use_change_filter", "label": "启用涨幅过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_change", "label": "最低涨幅%", "type": "float",
|
||||
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
|
||||
],
|
||||
@@ -28,7 +32,9 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_turnover_filter", True):
|
||||
expr = expr & (pl.col("turnover_rate") > min_to)
|
||||
if params.get("use_change_filter", True):
|
||||
expr = expr & (pl.col("change_pct") > min_chg)
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,12 @@ META = {
|
||||
"description": "连板股 + 今日涨幅 > 5%, 连板接力追踪",
|
||||
"tags": ["涨停", "连板", "接力"],
|
||||
"params": [
|
||||
{"id": "use_change_filter", "label": "启用涨幅过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_change", "label": "最低涨幅%", "type": "float",
|
||||
"default": 5.0, "min": 2.0, "max": 15.0, "step": 0.5},
|
||||
{"id": "use_boards_filter", "label": "启用连板数过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_boards", "label": "最少连板", "type": "int",
|
||||
"default": 1, "min": 1, "max": 10, "step": 1},
|
||||
],
|
||||
@@ -28,7 +32,9 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_change_filter", True):
|
||||
expr = expr & (pl.col("change_pct") > min_chg)
|
||||
if params.get("use_boards_filter", True):
|
||||
expr = expr & (pl.col("consecutive_limit_ups") >= min_boards)
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,14 @@ META = {
|
||||
"description": "20日动量为正 + 年化波动 < 30% + MA20上方",
|
||||
"tags": ["低波动", "龙头"],
|
||||
"params": [
|
||||
{"id": "require_positive_momentum", "label": "要求20日动量为正", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volatility_filter", "label": "启用波动率过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_max", "label": "最大年化波动", "type": "float",
|
||||
"default": 0.30, "min": 0.05, "max": 1.0, "step": 0.01},
|
||||
{"id": "require_above_ma20", "label": "要求收盘价在MA20上方", "type": "bool",
|
||||
"default": True},
|
||||
],
|
||||
"scoring": {"momentum_60d": 0.4, "momentum_20d": 0.3, "turnover_rate": 0.3},
|
||||
"order_by": "score",
|
||||
@@ -25,8 +31,11 @@ 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"))
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_positive_momentum", True):
|
||||
expr = expr & (pl.col("momentum_20d") > 0)
|
||||
if params.get("use_volatility_filter", True):
|
||||
expr = expr & (pl.col("annual_vol_20d") < vol_max)
|
||||
if params.get("require_above_ma20", True):
|
||||
expr = expr & (pl.col("close") > pl.col("ma20"))
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,14 @@ META = {
|
||||
"description": "MA5上穿MA20当日触发, 量能配合",
|
||||
"tags": ["均线", "金叉"],
|
||||
"params": [
|
||||
{"id": "require_ma_golden", "label": "要求MA5上穿MA20", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 1.2, "min": 0.5, "max": 5.0, "step": 0.1},
|
||||
{"id": "require_above_ma60", "label": "要求收盘价在MA60上方", "type": "bool",
|
||||
"default": True},
|
||||
],
|
||||
"scoring": {"momentum_20d": 0.5, "vol_ratio_5d": 0.3, "change_pct": 0.2},
|
||||
"order_by": "score",
|
||||
@@ -25,8 +31,11 @@ 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"))
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_ma_golden", True):
|
||||
expr = expr & pl.col("signal_ma_golden_5_20").fill_null(False)
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
if params.get("require_above_ma60", True):
|
||||
expr = expr & (pl.col("close") > pl.col("ma60"))
|
||||
return expr
|
||||
|
||||
@@ -7,6 +7,10 @@ META = {
|
||||
"description": "MACD金叉当日 + 量能放大",
|
||||
"tags": ["MACD", "金叉", "放量"],
|
||||
"params": [
|
||||
{"id": "require_macd_golden", "label": "要求MACD金叉", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 1.5, "min": 0.5, "max": 5.0, "step": 0.1},
|
||||
],
|
||||
@@ -25,7 +29,9 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_macd_golden", True):
|
||||
expr = expr & pl.col("signal_macd_golden").fill_null(False)
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
return expr
|
||||
|
||||
@@ -7,6 +7,12 @@ META = {
|
||||
"description": "触及60日新低后当日收阳放量, 反转信号",
|
||||
"tags": ["反转", "新低"],
|
||||
"params": [
|
||||
{"id": "require_n_day_low", "label": "要求60日新低", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "require_bullish_candle", "label": "要求收阳", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 1.5, "min": 0.5, "max": 5.0, "step": 0.1},
|
||||
],
|
||||
@@ -25,8 +31,11 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_n_day_low", True):
|
||||
expr = expr & pl.col("signal_n_day_low").fill_null(False)
|
||||
if params.get("require_bullish_candle", True):
|
||||
expr = expr & (pl.col("close") > pl.col("open"))
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
return expr
|
||||
|
||||
@@ -27,8 +27,12 @@ META = {
|
||||
"description": "涨幅 > 7% 且距涨停 < 3%, 追涨信号",
|
||||
"tags": ["涨停", "追涨"],
|
||||
"params": [
|
||||
{"id": "use_change_filter", "label": "启用涨幅过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_change", "label": "最低涨幅%", "type": "float",
|
||||
"default": 7.0, "min": 3.0, "max": 15.0, "step": 1.0},
|
||||
{"id": "use_limit_gap_filter", "label": "启用距涨停空间过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "limit_gap", "label": "距涨停空间%", "type": "float",
|
||||
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
|
||||
],
|
||||
@@ -49,7 +53,9 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_change_filter", True):
|
||||
expr = expr & (pl.col("change_pct") > min_chg)
|
||||
if params.get("use_limit_gap_filter", True):
|
||||
expr = expr & (pl.col("change_pct") < lp - gap)
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,14 @@ META = {
|
||||
"description": "RSI14 < 30超卖区 + 当日收阳 + 放量, 抄底信号",
|
||||
"tags": ["超跌", "反弹", "RSI"],
|
||||
"params": [
|
||||
{"id": "use_rsi_filter", "label": "启用RSI过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "rsi_max", "label": "RSI上限", "type": "float",
|
||||
"default": 30.0, "min": 10.0, "max": 50.0, "step": 1.0},
|
||||
{"id": "require_bullish_candle", "label": "要求收阳", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 1.2, "min": 0.5, "max": 5.0, "step": 0.1},
|
||||
],
|
||||
@@ -30,8 +36,11 @@ ALERTS = [
|
||||
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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_rsi_filter", True):
|
||||
expr = expr & (pl.col("rsi_14") < rsi_max)
|
||||
if params.get("require_bullish_candle", True):
|
||||
expr = expr & (pl.col("close") > pl.col("open"))
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
return expr
|
||||
|
||||
@@ -7,10 +7,16 @@ META = {
|
||||
"description": "RSI14 < 30超卖 + 涨幅 > 1% + 站上MA5, 超卖反转信号",
|
||||
"tags": ["超跌", "反弹", "RSI"],
|
||||
"params": [
|
||||
{"id": "use_rsi_filter", "label": "启用RSI过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "rsi_max", "label": "RSI上限", "type": "float",
|
||||
"default": 30.0, "min": 10.0, "max": 50.0, "step": 1.0},
|
||||
{"id": "use_change_filter", "label": "启用涨幅过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_change", "label": "最低涨幅%", "type": "float",
|
||||
"default": 1.0, "min": 0.5, "max": 5.0, "step": 0.5},
|
||||
{"id": "require_above_ma5", "label": "要求收盘价在MA5上方", "type": "bool",
|
||||
"default": True},
|
||||
],
|
||||
"scoring": {"change_pct": 0.4, "rsi_14": 0.3, "vol_ratio_5d": 0.3},
|
||||
"order_by": "score",
|
||||
@@ -30,8 +36,11 @@ ALERTS = [
|
||||
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"))
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_rsi_filter", True):
|
||||
expr = expr & (pl.col("rsi_14") < rsi_max)
|
||||
if params.get("use_change_filter", True):
|
||||
expr = expr & (pl.col("change_pct") > min_chg)
|
||||
if params.get("require_above_ma5", True):
|
||||
expr = expr & (pl.col("close") > pl.col("ma5"))
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,14 @@ META = {
|
||||
"description": "价格在MA20附近(±2%)且MA5>MA20>MA60多头排列, 回踩买入",
|
||||
"tags": ["回踩", "均线", "反弹"],
|
||||
"params": [
|
||||
{"id": "use_ma20_proximity", "label": "启用MA20附近过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "ma_proximity", "label": "MA偏离度%", "type": "float",
|
||||
"default": 2.0, "min": 0.5, "max": 5.0, "step": 0.5},
|
||||
{"id": "require_ma_alignment", "label": "要求MA5>MA20>MA60", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "require_positive_change", "label": "要求当日上涨", "type": "bool",
|
||||
"default": True},
|
||||
],
|
||||
"scoring": {"momentum_60d": 0.4, "change_pct": 0.3, "momentum_20d": 0.3},
|
||||
"order_by": "score",
|
||||
@@ -25,10 +31,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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_ma20_proximity", True):
|
||||
expr = (
|
||||
expr
|
||||
& (pl.col("close") > pl.col("ma20") * (1 - proximity))
|
||||
& (pl.col("close") < pl.col("ma20") * (1 + proximity))
|
||||
)
|
||||
if params.get("require_ma_alignment", True):
|
||||
expr = expr & (pl.col("ma5") > pl.col("ma20")) & (pl.col("ma20") > pl.col("ma60"))
|
||||
if params.get("require_positive_change", True):
|
||||
expr = expr & (pl.col("change_pct") > 0)
|
||||
return expr
|
||||
|
||||
@@ -7,10 +7,18 @@ META = {
|
||||
"description": "回踩MA20附近 + 缩量 + 中期趋势向上",
|
||||
"tags": ["回踩", "支撑"],
|
||||
"params": [
|
||||
{"id": "use_ma20_proximity", "label": "启用MA20附近过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "ma_proximity", "label": "均线偏离度", "type": "float",
|
||||
"default": 0.02, "min": 0.01, "max": 0.05, "step": 0.005},
|
||||
{"id": "use_volume_filter", "label": "启用缩量过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_max", "label": "最大量比", "type": "float",
|
||||
"default": 0.8, "min": 0.2, "max": 1.5, "step": 0.1},
|
||||
{"id": "require_above_ma60", "label": "要求收盘价在MA60上方", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "require_positive_momentum", "label": "要求20日动量为正", "type": "bool",
|
||||
"default": True},
|
||||
],
|
||||
"scoring": {"momentum_60d": 0.4, "momentum_20d": 0.3, "turnover_rate": 0.3},
|
||||
"order_by": "score",
|
||||
@@ -28,10 +36,17 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_ma20_proximity", True):
|
||||
expr = (
|
||||
expr
|
||||
& (pl.col("close") > pl.col("ma20") * (1 - proximity))
|
||||
& (pl.col("close") < pl.col("ma20") * (1 + proximity))
|
||||
)
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") < vol_max)
|
||||
if params.get("require_above_ma60", True):
|
||||
expr = expr & (pl.col("close") > pl.col("ma60"))
|
||||
if params.get("require_positive_momentum", True):
|
||||
expr = expr & (pl.col("momentum_20d") > 0)
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,14 @@ META = {
|
||||
"description": "高开 > 3% 且收盘高于开盘价, 集合竞价强势",
|
||||
"tags": ["高开", "强势"],
|
||||
"params": [
|
||||
{"id": "use_open_gap_filter", "label": "启用高开过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_open_gap", "label": "最低高开%", "type": "float",
|
||||
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
|
||||
{"id": "require_close_above_open", "label": "要求收盘高于开盘", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_change_filter", "label": "启用涨幅过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "min_change", "label": "最低涨幅%", "type": "float",
|
||||
"default": 3.0, "min": 1.0, "max": 10.0, "step": 0.5},
|
||||
],
|
||||
@@ -28,8 +34,11 @@ 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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_open_gap_filter", True):
|
||||
expr = expr & (pl.col("open") > pl.col("prev_close") * (1 + min_gap))
|
||||
if params.get("require_close_above_open", True):
|
||||
expr = expr & (pl.col("close") > pl.col("open"))
|
||||
if params.get("use_change_filter", True):
|
||||
expr = expr & (pl.col("change_pct") > min_chg)
|
||||
return expr
|
||||
|
||||
@@ -15,6 +15,12 @@ META = {
|
||||
"exclude_new_days": 60,
|
||||
},
|
||||
"params": [
|
||||
{"id": "require_above_ma60", "label": "要求收盘价在MA60上方", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "require_n_day_high", "label": "要求60日新高", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 2.0, "min": 0.5, "max": 10.0, "step": 0.1},
|
||||
],
|
||||
@@ -35,8 +41,11 @@ ALERTS = [
|
||||
|
||||
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)
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_above_ma60", True):
|
||||
expr = expr & (pl.col("close") > pl.col("ma60"))
|
||||
if params.get("require_n_day_high", True):
|
||||
expr = expr & pl.col("signal_n_day_high").fill_null(False)
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
return expr
|
||||
|
||||
@@ -7,8 +7,14 @@ META = {
|
||||
"description": "突破MA20 + 放量 + 收阳",
|
||||
"tags": ["量价", "突破"],
|
||||
"params": [
|
||||
{"id": "require_ma20_breakout", "label": "要求突破MA20", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "use_volume_filter", "label": "启用量比过滤", "type": "bool",
|
||||
"default": True},
|
||||
{"id": "vol_ratio_min", "label": "最低量比", "type": "float",
|
||||
"default": 2.0, "min": 0.5, "max": 10.0, "step": 0.1},
|
||||
{"id": "require_bullish_candle", "label": "要求收阳", "type": "bool",
|
||||
"default": True},
|
||||
],
|
||||
"scoring": {"vol_ratio_5d": 0.4, "change_pct": 0.3, "momentum_20d": 0.3},
|
||||
"order_by": "score",
|
||||
@@ -25,8 +31,11 @@ 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"))
|
||||
)
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("require_ma20_breakout", True):
|
||||
expr = expr & pl.col("signal_ma20_breakout").fill_null(False)
|
||||
if params.get("use_volume_filter", True):
|
||||
expr = expr & (pl.col("vol_ratio_5d") >= vol_min)
|
||||
if params.get("require_bullish_candle", True):
|
||||
expr = expr & (pl.col("close") > pl.col("open"))
|
||||
return expr
|
||||
|
||||
@@ -149,6 +149,10 @@ const strategyDefaultParams = (detail: StrategyDetail) => {
|
||||
})
|
||||
return values
|
||||
}
|
||||
const mergeStrategyParams = (detail: StrategyDetail, values?: Record<string, any> | null) => ({
|
||||
...strategyDefaultParams(detail),
|
||||
...(values ?? {}),
|
||||
})
|
||||
const buildDefaultOverrides = (detail: StrategyDetail) => ({
|
||||
basic_filter: { ...detail.basic_filter },
|
||||
entry_signals: detail.entry_signals.map(toSignalId),
|
||||
@@ -745,7 +749,7 @@ export function StrategyBacktest() {
|
||||
if (!detail || loadedStrategyRef.current === detail.id) return
|
||||
loadedStrategyRef.current = detail.id
|
||||
if (saved?.selectedStrategy === detail.id && (saved.params || saved.overrides)) {
|
||||
setStrategyParams(saved.params ?? strategyDefaultParams(detail))
|
||||
setStrategyParams(mergeStrategyParams(detail, saved.params))
|
||||
setOverrides(saved.overrides ?? buildDefaultOverrides(detail))
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user