关键位箱体突破调整
This commit is contained in:
@@ -93,9 +93,13 @@ KEY_CONFIRM_BAR=-1
|
||||
# 【量能】突破棒成交量 > 前 N 根均量 × 倍数(默认 N=20,倍数=1.3 即放大 30%)
|
||||
KEY_VOLUME_MA_BARS=20
|
||||
KEY_VOLUME_RATIO_MIN=1.3
|
||||
# 【突破K实体幅度】占开盘价百分比区间(须同时满足有效突破)
|
||||
# 【箱体/收敛】突破K收盘越过关键位(占该侧价格%)的下限;无上限(过猛由计划RR过滤)
|
||||
KEY_BREAKOUT_AMP_MIN_PCT=0.03
|
||||
# 已不参与门控,可保留配置项兼容旧环境
|
||||
KEY_BREAKOUT_AMP_MAX_PCT=0.5
|
||||
# 【阻力/支撑】突破后微信提醒次数与间隔(分钟)
|
||||
KEY_ALERT_MAX_TIMES=3
|
||||
KEY_ALERT_INTERVAL_MINUTES=5
|
||||
# 【日成交量排名】品种须在该排名前 N 名(添加关键位与运行时门控均校验)
|
||||
KEY_DAILY_VOLUME_RANK_MAX=30
|
||||
# 【关键位自动开仓盈亏比】按确认K收盘 E 计算,严格大于该值才市价开仓(如 1.5 表示须 >1.5:1)
|
||||
@@ -104,8 +108,6 @@ KEY_AUTO_MIN_PLANNED_RR=1.5
|
||||
KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5
|
||||
# 趋势单方案:止损在突破 K 极值外侧的百分比(默认 1 即 1%)
|
||||
KEY_TREND_STOP_OUTSIDE_PCT=1
|
||||
KEY_ALERT_MAX_TIMES=3
|
||||
KEY_ALERT_INTERVAL_MINUTES=5
|
||||
|
||||
# =============================================================================
|
||||
# 交易执行 / 人工风控(页面「实盘下单」)
|
||||
|
||||
+210
-54
@@ -54,6 +54,19 @@ from key_sl_tp_lib import (
|
||||
sl_tp_mode_label,
|
||||
sl_tp_plan_summary_text,
|
||||
)
|
||||
from key_monitor_lib import (
|
||||
KEY_DIRECTION_WATCH,
|
||||
KEY_MONITOR_ALERT_ONLY_TYPES,
|
||||
KEY_MONITOR_AUTO_TYPES,
|
||||
KEY_MONITOR_RS_TYPES,
|
||||
auto_amp_ok,
|
||||
auto_confirm_ok,
|
||||
detect_rs_box_break,
|
||||
format_auto_amp_line,
|
||||
format_auto_confirm_line,
|
||||
notify_interval_elapsed,
|
||||
rs_break_from_direction,
|
||||
)
|
||||
from hub_auth import request_allowed as hub_request_allowed
|
||||
from history_window_lib import (
|
||||
PRESET_CUSTOM,
|
||||
@@ -175,7 +188,7 @@ KEY_CONFIRM_BAR = int(os.getenv("KEY_CONFIRM_BAR", "-1"))
|
||||
KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT = os.getenv("KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT", "true").lower() == "true"
|
||||
ORDER_MONITOR_TYPE_MANUAL = "下单监控"
|
||||
ORDER_MONITOR_TYPE_KEY_AUTO = "关键位监控"
|
||||
KEY_MONITOR_AUTO_TYPES = frozenset({"箱体突破", "收敛突破"})
|
||||
# KEY_MONITOR_AUTO_TYPES / KEY_MONITOR_ALERT_ONLY_TYPES:见 key_monitor_lib
|
||||
# 与币安 App「仓位历史-实现盈亏」对齐:默认仅 REALIZED_PNL(手续费另计;避免与 COMMISSION 重复扣)
|
||||
BINANCE_APP_PNL_INCOME_TYPES = frozenset({"REALIZED_PNL"})
|
||||
BINANCE_APP_PNL_INCOME_WITH_FEE = frozenset({"REALIZED_PNL", "COMMISSION"})
|
||||
@@ -187,7 +200,6 @@ BINANCE_PNL_INCLUDE_FUNDING = os.getenv("BINANCE_PNL_INCLUDE_FUNDING", "false").
|
||||
"true",
|
||||
"yes",
|
||||
)
|
||||
KEY_MONITOR_ALERT_ONLY_TYPES = frozenset({"关键阻力位", "关键支撑位"})
|
||||
AUTO_TRANSFER_ENABLED = os.getenv("AUTO_TRANSFER_ENABLED", "false").lower() == "true"
|
||||
AUTO_TRANSFER_AMOUNT = float(os.getenv("AUTO_TRANSFER_AMOUNT", "30"))
|
||||
AUTO_TRANSFER_FROM = os.getenv("AUTO_TRANSFER_FROM", "funding")
|
||||
@@ -4145,19 +4157,17 @@ def _key_hard_checks(symbol, direction, upper, lower, monitor_type):
|
||||
avg20 = sum(float(x[5]) for x in prev_vol) / max(len(prev_vol), 1)
|
||||
vol_break = float(breakout[5])
|
||||
vol_ok = vol_break > avg20 * KEY_VOLUME_RATIO_MIN if avg20 > 0 else False
|
||||
open_b = float(breakout[1])
|
||||
close_b = float(breakout[4])
|
||||
high_b = float(breakout[2])
|
||||
low_b = float(breakout[3])
|
||||
amp_pct = abs(close_b - open_b) / open_b * 100 if open_b > 0 else 0
|
||||
amp_ok = (amp_pct > KEY_BREAKOUT_AMP_MIN_PCT) and (amp_pct < KEY_BREAKOUT_AMP_MAX_PCT)
|
||||
cfm_close = float(confirm[4])
|
||||
# 区间极值点严格以前端录入 upper/lower 为准:做多看上沿,做空看下沿
|
||||
edge = float(upper) if direction == "long" else float(lower)
|
||||
breakout_ok = (close_b > float(upper)) if direction == "long" else (close_b < float(lower))
|
||||
confirm_ok_raw = (cfm_close > edge) if direction == "long" else (cfm_close < edge)
|
||||
# 口径收紧:未发生有效突破时,不标记幅度/二确通过,避免出现“还没到位却显示Y”
|
||||
amp_ok, amp_pct = auto_amp_ok(
|
||||
direction, close_b, float(upper), float(lower), KEY_BREAKOUT_AMP_MIN_PCT
|
||||
)
|
||||
amp_ok = amp_ok and breakout_ok
|
||||
confirm_ok_raw = auto_confirm_ok(direction, cfm_close, float(upper), float(lower))
|
||||
confirm_ok = confirm_ok_raw and breakout_ok
|
||||
rank, total = _daily_volume_rank(symbol)
|
||||
rank_ok = (rank is not None) and (rank <= KEY_DAILY_VOLUME_RANK_MAX)
|
||||
@@ -4219,13 +4229,130 @@ def _finalize_key_monitor_one_shot(conn, row, last_msg, close_reason):
|
||||
conn.execute("DELETE FROM key_monitors WHERE id=?", (row["id"],))
|
||||
|
||||
|
||||
def _fetch_last_closed_bar(symbol):
|
||||
"""最近一根闭合 K:[ts, o, h, l, c, v] 或 None。"""
|
||||
ex_sym = normalize_exchange_symbol(symbol)
|
||||
bars = exchange.fetch_ohlcv(ex_sym, timeframe=KLINE_TIMEFRAME, limit=5) or []
|
||||
if len(bars) < 2:
|
||||
return None
|
||||
closed = bars[:-1]
|
||||
return closed[-1] if closed else None
|
||||
|
||||
|
||||
def build_wechat_rs_level_message(
|
||||
symbol,
|
||||
monitor_type,
|
||||
trigger_time,
|
||||
upper,
|
||||
lower,
|
||||
trigger_close,
|
||||
break_info,
|
||||
notify_index,
|
||||
notify_max,
|
||||
):
|
||||
lines = [
|
||||
f"# 📌 {symbol} 关键位突破提醒({notify_index}/{notify_max})",
|
||||
f"**账户:{_wechat_account_label()}**",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"### 突破判定(5m 收盘)",
|
||||
f"- 类型:**{monitor_type}**",
|
||||
f"- 触发时间:`{trigger_time}`",
|
||||
f"- 上沿:`{upper}`|下沿:`{lower}`",
|
||||
f"- 触发收盘:`{format_price_for_symbol(symbol, trigger_close)}`",
|
||||
f"- **{break_info['break_label']}**(程序推断:**{_wechat_direction_text(break_info['direction'])}**)",
|
||||
f"- 突破价位:`{format_price_for_symbol(symbol, break_info['edge_price'])}`",
|
||||
"",
|
||||
"### 说明",
|
||||
"- 本条为**人工盯盘**用途:录入时**不选多空**,由上/下沿突破方向自动判定。",
|
||||
f"- 共推送 **{notify_max}** 次(间隔约 {KEY_ALERT_INTERVAL_MINUTES} 分钟),推送完毕后本条监控结案。",
|
||||
"- **不参与**自动开仓、量能/二确/盈亏比门控。",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _key_rs_gate_preview(symbol, upper, lower):
|
||||
"""页面门控预览:阻力/支撑仅显示距上/下沿与是否已越线。"""
|
||||
bar = _fetch_last_closed_bar(symbol)
|
||||
if not bar:
|
||||
return {"summary": "5m数据不足", "metrics": ""}
|
||||
close = float(bar[4])
|
||||
br = detect_rs_box_break(close, upper, lower)
|
||||
if br:
|
||||
return {
|
||||
"summary": f"已越线:{br['break_label']}",
|
||||
"metrics": f"收盘:{format_price_for_symbol(symbol, close)}",
|
||||
}
|
||||
return {
|
||||
"summary": "待突破",
|
||||
"metrics": f"收盘:{format_price_for_symbol(symbol, close)}",
|
||||
}
|
||||
|
||||
|
||||
def _process_key_rs_level_alert(conn, row):
|
||||
"""关键阻力位/支撑位:5m 收盘越上沿或下沿后,按间隔推送最多 KEY_ALERT_MAX_TIMES 次。"""
|
||||
sym = row["symbol"]
|
||||
typ = (row["monitor_type"] or "").strip()
|
||||
up, low = float(row["upper"]), float(row["lower"])
|
||||
if up <= low:
|
||||
return
|
||||
bar = _fetch_last_closed_bar(sym)
|
||||
if not bar:
|
||||
return
|
||||
close = float(bar[4])
|
||||
ts = bar[0]
|
||||
count = int(row["notification_count"] or 0)
|
||||
max_n = max(1, int(row["max_notify"] or KEY_ALERT_MAX_TIMES))
|
||||
interval = max(1, int(row["notify_interval_min"] or KEY_ALERT_INTERVAL_MINUTES))
|
||||
now_dt = app_now()
|
||||
|
||||
if count == 0:
|
||||
br = detect_rs_box_break(close, up, low)
|
||||
if not br:
|
||||
return
|
||||
else:
|
||||
if not notify_interval_elapsed(row["last_notified_at"], interval, now_dt):
|
||||
return
|
||||
br = rs_break_from_direction(row["direction"], up, low)
|
||||
if not br:
|
||||
return
|
||||
|
||||
trigger_time = ms_to_app_local_str(int(ts)) if ts else app_now_str()
|
||||
notify_index = count + 1
|
||||
msg = build_wechat_rs_level_message(
|
||||
symbol=sym,
|
||||
monitor_type=typ,
|
||||
trigger_time=trigger_time,
|
||||
upper=up,
|
||||
lower=low,
|
||||
trigger_close=close,
|
||||
break_info=br,
|
||||
notify_index=notify_index,
|
||||
notify_max=max_n,
|
||||
)
|
||||
send_wechat_msg(msg)
|
||||
conn.execute(
|
||||
"UPDATE key_monitors SET direction=?, notification_count=?, last_notified_at=?, last_alert_message=? WHERE id=?",
|
||||
(br["direction"], notify_index, app_now_str(), msg, row["id"]),
|
||||
)
|
||||
if notify_index >= max_n:
|
||||
hist_row = conn.execute("SELECT * FROM key_monitors WHERE id=?", (row["id"],)).fetchone()
|
||||
if hist_row:
|
||||
insert_key_monitor_history(conn, hist_row, notify_index, msg, "key_level_alert_done")
|
||||
conn.execute("DELETE FROM key_monitors WHERE id=?", (row["id"],))
|
||||
|
||||
|
||||
def _key_hard_lines_from_checks(checks):
|
||||
direction = (checks.get("direction") or "long").lower()
|
||||
return [
|
||||
f"量能:{'通过' if checks['vol_ok'] else '不通过'}(突破K量 {round(checks['vol_break'], 4)} / 前20均量 {round(checks['avg20'], 4)},阈值1.3x)",
|
||||
f"突破价位:{'通过' if checks['breakout_ok'] else '不通过'}(突破K收盘 {round(float(checks['breakout_close']), 8)},关键位 {checks['edge_price']})",
|
||||
f"突破K幅度:{'通过' if checks['amp_ok'] else '不通过'}({round(checks['amp_pct'], 4)}%,要求0.03%~0.5%)",
|
||||
f"第二根确认:{'通过' if checks['confirm_ok'] else '不通过'}(确认收盘 {checks['confirm_close']},关键位 {checks['edge_price']})",
|
||||
f"日成交量排名:{'通过' if checks['rank_ok'] else '不通过'}({checks['rank']}/{checks['rank_total']},要求前30)",
|
||||
format_auto_amp_line(checks["amp_ok"], checks["amp_pct"], KEY_BREAKOUT_AMP_MIN_PCT),
|
||||
format_auto_confirm_line(
|
||||
checks["confirm_ok"], checks["confirm_close"], checks["edge_price"], direction
|
||||
),
|
||||
f"日成交量排名:{'通过' if checks['rank_ok'] else '不通过'}({checks['rank']}/{checks['rank_total']},要求前{KEY_DAILY_VOLUME_RANK_MAX})",
|
||||
]
|
||||
|
||||
|
||||
@@ -4836,7 +4963,7 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, br
|
||||
return True, None
|
||||
|
||||
|
||||
# 关键位监控(箱体/收敛可自动开仓;阻力/支撑位仅单次提醒结案)
|
||||
# 关键位监控(箱体/收敛可自动开仓;阻力/支撑为双向 5m 收盘突破 + 三次提醒)
|
||||
def check_key_monitors():
|
||||
conn = get_db()
|
||||
rows = conn.execute("SELECT * FROM key_monitors").fetchall()
|
||||
@@ -4845,7 +4972,16 @@ def check_key_monitors():
|
||||
typ = (typ_raw or "").strip()
|
||||
if is_fib_key_monitor_type(typ):
|
||||
continue
|
||||
if typ in KEY_MONITOR_RS_TYPES:
|
||||
try:
|
||||
_process_key_rs_level_alert(conn, r)
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
|
||||
direction = (r["direction"] or "long").lower()
|
||||
if direction == KEY_DIRECTION_WATCH:
|
||||
continue
|
||||
try:
|
||||
checks = _key_hard_checks(sym, direction, up, low, typ)
|
||||
except Exception:
|
||||
@@ -4863,31 +4999,7 @@ def check_key_monitors():
|
||||
hard_lines = _key_hard_lines_from_checks(checks)
|
||||
trigger_time = ms_to_app_local_str(int(checks["confirm_ts"])) if checks.get("confirm_ts") else app_now_str()
|
||||
|
||||
alert_only = typ in KEY_MONITOR_ALERT_ONLY_TYPES or (
|
||||
typ not in KEY_MONITOR_AUTO_TYPES and typ not in KEY_MONITOR_ALERT_ONLY_TYPES
|
||||
)
|
||||
|
||||
if alert_only:
|
||||
op_lines = [
|
||||
"- 本条为关键阻力/支撑或非标类型:**仅单次推送**,不进行自动开仓。",
|
||||
"- 本条关键位将在推送后记入历史并从监控列表移除。",
|
||||
]
|
||||
msg = build_wechat_key_monitor_message(
|
||||
symbol=sym,
|
||||
direction=direction,
|
||||
monitor_type=typ,
|
||||
trigger_time=trigger_time,
|
||||
key_price=key_price,
|
||||
confirm_close=checks["confirm_close"],
|
||||
hard_lines=hard_lines,
|
||||
btc8h_status=btc8h_status,
|
||||
coin4h_status=coin4h_status,
|
||||
swing4h_pct=checks.get("swing4h_pct") or 0.0,
|
||||
op_lines=op_lines,
|
||||
risk_tip=risk_tip,
|
||||
)
|
||||
send_wechat_msg(msg)
|
||||
_finalize_key_monitor_one_shot(conn, r, msg, "key_level_alert_only")
|
||||
if typ not in KEY_MONITOR_AUTO_TYPES:
|
||||
continue
|
||||
|
||||
plan_tuple, sl_tp_mode = _key_plan_sl_tp_for_row(r, direction, up, low, checks)
|
||||
@@ -5665,11 +5777,10 @@ def render_main_page(page="trade"):
|
||||
active_count = len(order_list)
|
||||
can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS
|
||||
key_gate_rule_text = (
|
||||
f"周期 {KLINE_TIMEFRAME}|确认K:突破棒偏移 {KEY_CONFIRM_BREAKOUT_BAR}、确认棒偏移 {KEY_CONFIRM_BAR}|"
|
||||
f"量能:突破量 > 前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}|"
|
||||
f"自动开仓盈亏比 > {KEY_AUTO_MIN_PLANNED_RR}:1|日成交量排名前 {KEY_DAILY_VOLUME_RANK_MAX}|"
|
||||
f"箱体/收敛可选 SL/TP 方案(标准 / 箱体1R·止盈1.5H / 趋势单+自填止盈)|移动保本默认关|"
|
||||
f"斐波:限价 @ E(SL/TP 为 H/L),可选移动保本|趋势止损外侧 {KEY_TREND_STOP_OUTSIDE_PCT}%"
|
||||
f"【箱体/收敛】{KLINE_TIMEFRAME} 两根闭合K|突破越过关键位 > {KEY_BREAKOUT_AMP_MIN_PCT}%|"
|
||||
f"确认K收于箱外|量能>前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}|"
|
||||
f"RR>{KEY_AUTO_MIN_PLANNED_RR}|日成交前{KEY_DAILY_VOLUME_RANK_MAX}|"
|
||||
f"【阻力/支撑】填上/下沿,5m 收盘突破任一侧即提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分),不选方向、不自动开仓"
|
||||
)
|
||||
strategy_extra = {}
|
||||
if page in ("strategy", "strategy_trend", "strategy_roll"):
|
||||
@@ -5856,9 +5967,22 @@ def api_price_snapshot():
|
||||
gate_summary = f"斐波 挂E={entry_txt} {'标记价将失效' if inval else '等待成交'}"
|
||||
if _sqlite_row_val(r, "fib_limit_order_id"):
|
||||
gate_metrics = f"限价单:{_sqlite_row_val(r, 'fib_limit_order_id')}"
|
||||
elif (r["monitor_type"] or "").strip() in KEY_MONITOR_RS_TYPES:
|
||||
try:
|
||||
prev = _key_rs_gate_preview(r["symbol"], r["upper"], r["lower"])
|
||||
gate_summary = prev.get("summary") or "-"
|
||||
gate_metrics = prev.get("metrics") or ""
|
||||
except Exception:
|
||||
gate_summary = "-"
|
||||
else:
|
||||
try:
|
||||
gate = _key_hard_checks(r["symbol"], (r["direction"] or "long").lower(), r["upper"], r["lower"], r["monitor_type"])
|
||||
gate = _key_hard_checks(
|
||||
r["symbol"],
|
||||
(r["direction"] or "long").lower(),
|
||||
r["upper"],
|
||||
r["lower"],
|
||||
r["monitor_type"],
|
||||
)
|
||||
except Exception:
|
||||
gate = None
|
||||
if gate:
|
||||
@@ -6330,11 +6454,13 @@ def add_key():
|
||||
if not symbol:
|
||||
flash("symbol 不能为空")
|
||||
return redirect("/key_monitor")
|
||||
direction_sel = (d.get("direction") or "").strip().lower()
|
||||
if direction_sel not in ("long", "short"):
|
||||
flash("请选择做多或做空")
|
||||
return redirect("/key_monitor")
|
||||
mt = (d.get("type") or "").strip()
|
||||
direction_sel = (d.get("direction") or "").strip().lower()
|
||||
if mt in KEY_MONITOR_RS_TYPES:
|
||||
direction_sel = KEY_DIRECTION_WATCH
|
||||
elif direction_sel not in ("long", "short"):
|
||||
flash("箱体/收敛突破请选择做多或做空")
|
||||
return redirect("/key_monitor")
|
||||
allowed_types = (
|
||||
tuple(KEY_MONITOR_AUTO_TYPES)
|
||||
+ tuple(KEY_MONITOR_ALERT_ONLY_TYPES)
|
||||
@@ -6369,6 +6495,10 @@ def add_key():
|
||||
lw = round_price_to_exchange(ex_sym_key, float(d["lower"]))
|
||||
upper_px = float(uh) if uh is not None else float(d["upper"])
|
||||
lower_px = float(lw) if lw is not None else float(d["lower"])
|
||||
if upper_px <= lower_px:
|
||||
conn.close()
|
||||
flash("上沿必须大于下沿")
|
||||
return redirect("/key_monitor")
|
||||
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
|
||||
if is_fib_key_monitor_type(mt):
|
||||
ok_fib, err_fib = _add_fib_key_monitor(
|
||||
@@ -6408,12 +6538,32 @@ def add_key():
|
||||
mtpx = round_price_to_exchange(ex_sym_key, manual_tp)
|
||||
if mtpx is not None:
|
||||
manual_tp = float(mtpx)
|
||||
conn.execute(
|
||||
"INSERT INTO key_monitors "
|
||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
||||
"VALUES (?,?,?,?,?,?,?,?)",
|
||||
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
||||
)
|
||||
if mt in KEY_MONITOR_RS_TYPES:
|
||||
conn.execute(
|
||||
"INSERT INTO key_monitors "
|
||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||
"max_notify,notify_interval_min) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||
(
|
||||
symbol,
|
||||
mt,
|
||||
direction_sel,
|
||||
upper_px,
|
||||
lower_px,
|
||||
sl_tp_mode,
|
||||
manual_tp,
|
||||
be_flag,
|
||||
KEY_ALERT_MAX_TIMES,
|
||||
KEY_ALERT_INTERVAL_MINUTES,
|
||||
),
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
"INSERT INTO key_monitors "
|
||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
||||
"VALUES (?,?,?,?,?,?,?,?)",
|
||||
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
ctr = False
|
||||
@@ -6427,7 +6577,13 @@ def add_key():
|
||||
extra = ""
|
||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
||||
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total}){extra}")
|
||||
if mt in KEY_MONITOR_RS_TYPES:
|
||||
flash(
|
||||
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
||||
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
||||
)
|
||||
else:
|
||||
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total}){extra}")
|
||||
if ctr:
|
||||
flash(
|
||||
"⚠️ 4h EMA55 提示:当前与所选方向逆势;「箱体突破/收敛突破」在条件满足时仍会按计划自动市价开仓,请注意仓位。"
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
<option value="关键阻力位">关键阻力位</option>
|
||||
<option value="关键支撑位">关键支撑位</option>
|
||||
</select>
|
||||
<select name="direction" required>
|
||||
<select name="direction" id="key-direction" required>
|
||||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||||
</select>
|
||||
<input name="upper" step="0.0001" placeholder="上沿/阻力" required>
|
||||
@@ -304,7 +304,11 @@
|
||||
<div class="pos-card-head">
|
||||
<div class="pos-card-symbol">
|
||||
<strong>{{ k.symbol }}</strong>
|
||||
{% if k.direction == 'watch' %}
|
||||
<span class="pos-side-badge" style="background:#2a3152;color:#9ab">双向</span>
|
||||
{% else %}
|
||||
<span class="pos-side-badge {{ 'pos-side-long' if k.direction == 'long' else 'pos-side-short' }}">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
||||
{% endif %}
|
||||
<span class="badge direction" style="margin-left:4px">{{ k.monitor_type }}</span>
|
||||
</div>
|
||||
<button type="button" class="pos-close-btn" style="border:none;cursor:pointer" onclick="deleteKeyMonitor({{ k.id }})">删</button>
|
||||
@@ -1406,6 +1410,7 @@ if(journalForm){
|
||||
|
||||
function syncKeyMonitorFormFields(){
|
||||
const typeEl = document.querySelector('#key-form [name="type"]');
|
||||
const dirEl = document.getElementById("key-direction");
|
||||
const modeEl = document.getElementById("key-sl-tp-mode");
|
||||
const manualTp = document.getElementById("key-manual-tp");
|
||||
const beWrap = document.getElementById("key-breakeven-wrap");
|
||||
@@ -1413,8 +1418,15 @@ function syncKeyMonitorFormFields(){
|
||||
const t = (typeEl.value || "").trim();
|
||||
const autoTypes = new Set(["箱体突破","收敛突破"]);
|
||||
const fibTypes = new Set(["斐波回调0.618","斐波回调0.786"]);
|
||||
const rsTypes = new Set(["关键阻力位","关键支撑位"]);
|
||||
const showAuto = autoTypes.has(t);
|
||||
const showBe = showAuto || fibTypes.has(t);
|
||||
const showDir = !rsTypes.has(t);
|
||||
if(dirEl){
|
||||
dirEl.style.display = showDir ? "" : "none";
|
||||
dirEl.required = showDir;
|
||||
if(!showDir) dirEl.value = "";
|
||||
}
|
||||
if(modeEl) modeEl.style.display = showAuto ? "" : "none";
|
||||
if(manualTp){
|
||||
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
||||
|
||||
@@ -1,101 +1,142 @@
|
||||
# 关键位自动下单说明
|
||||
# 关键位监控说明(自动开仓 + 人工盯盘)
|
||||
|
||||
**适用仓库:`crypto_monitor_binance`|交易所:Binance U 本位永续**(Gate 版见同名的 `crypto_monitor_gate` 目录。)
|
||||
**适用:`crypto_monitor_binance`(Binance U 本位永续)**
|
||||
Gate / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`。
|
||||
|
||||
本文档与 `.env`、`app.check_key_monitors`、`app.add_key`、`_market_open_for_key_monitor` 的实现一致。
|
||||
本文档与 `.env`、`check_key_monitors`、`add_key`、`_key_hard_checks`、`_process_key_rs_level_alert` 一致。
|
||||
|
||||
---
|
||||
|
||||
## 结构与是否自动开仓
|
||||
## 一、监控类型总览
|
||||
|
||||
| `key_monitors.monitor_type`(录入类型) | 自动下单 | 触发后处置 |
|
||||
|---------------------------------------|----------|------------|
|
||||
| **箱体突破** | 是(满足全部条件) | **一次性结案**:写 `key_monitor_history` → 从 `key_monitors` **删除** |
|
||||
| **收敛突破** | 是(同上) | 同上 |
|
||||
| **关键阻力位** | 否 | 企业微信 **1 次** → `close_reason=key_level_alert_only` → **失效** |
|
||||
| **关键支撑位** | 否 | 同上 |
|
||||
| 录入类型 | 录入时选方向 | 自动市价开仓 | 触发与结案 |
|
||||
|----------|--------------|--------------|------------|
|
||||
| **箱体突破** | **必选** 多/空 | **是**(门控 + RR) | 条件满足 → 开仓或 `rr_insufficient` / `exchange_failed` → **一次性删除** |
|
||||
| **收敛突破** | **必选** 多/空 | **是**(同上) | 同上 |
|
||||
| **关键阻力位** | **不选**(`direction=watch`) | **否** | 5m 收盘突破上/下沿 → 微信 **3 次** → `key_level_alert_done` |
|
||||
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
|
||||
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
|
||||
|
||||
触发条件:**5m 收线硬门控** `_key_hard_checks`(量能、突破幅度、第二根收盘确认、日成交量前 30 等)。
|
||||
**添加时(所有类型):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)**;上沿 **>** 下沿。
|
||||
|
||||
---
|
||||
|
||||
## 录入限制(`/add_key`)
|
||||
## 二、关键阻力位 / 关键支撑位(人工盯盘)
|
||||
|
||||
- 存在 **`order_monitors.status='active'`** 时:**禁止添加** 「箱体突破」「收敛突破」。
|
||||
- **关键阻力位 / 关键支撑位**:不受上条限制;触发后 **仅单次微信提醒**,然后结案。
|
||||
- **4h EMA55 与所选方向逆势**:**不拦截**;添加成功后 **Flash** 提示。
|
||||
- 上下沿入库前经 **`round_price_to_exchange`** 按合约 **价格精度** 取整。
|
||||
### 2.1 录入
|
||||
|
||||
---
|
||||
- 填写 **上沿 `upper`** 与 **下沿 `lower`**(程序同时监控两侧,**无法预先判定**做多还是做空)。
|
||||
- 页面 **不显示、不要求** 方向;库中 `direction` 初始为 `watch`,**首次突破后** 写入 `long`(向上突破上沿)或 `short`(向下突破下沿)。
|
||||
|
||||
## 环境与参数(`.env`)
|
||||
### 2.2 触发(极简)
|
||||
|
||||
| 变量 | 含义 | 默认 |
|
||||
- 周期:**`KLINE_TIMEFRAME`(默认 5m)最近一根已闭合 K** 的 **收盘价**(非影线)。
|
||||
- **向上突破上沿:** `收盘 > upper` → 推断方向 **多 / 向上**,本次监控任务开始按节奏提醒。
|
||||
- **向下突破下沿:** `收盘 < lower` → 推断方向 **空 / 向下**,本次任务同样开始提醒。
|
||||
- **任一侧突破即结束本条监控周期**(不会在突破后再等待另一侧;上沿、下沿谁先满足用谁,同根 K 仅可能满足一侧)。
|
||||
|
||||
**不参与:** 量能、二确 K、越过幅度下限、日成交排名(运行时)、计划 RR、自动开仓。
|
||||
|
||||
### 2.3 微信提醒次数
|
||||
|
||||
| 配置 | 默认 | 含义 |
|
||||
|------|------|------|
|
||||
| `KEY_AUTO_MIN_PLANNED_RR` | 计划 RR 阈值:**仅当严格大于该值** 才自动开仓(按下方 `E` 计算) | `1.5` |
|
||||
| `KEY_STOP_OUTSIDE_BREAKOUT_PCT` | 止损:突破 K 极值向外 **百分比**(多:`低×(1−p/100)`;空:`高×(1+p/100)`) | `0.5` |
|
||||
| `KEY_ALERT_MAX_TIMES` | `3` | 突破后最多推送 3 次 |
|
||||
| `KEY_ALERT_INTERVAL_MINUTES` | `5` | 相邻两次推送至少间隔 5 分钟 |
|
||||
|
||||
**其余与本仓库手动实盘一致:** `KLINE_TIMEFRAME`、`RISK_PERCENT`、`LIVE_TRADING_ENABLED`、`BREAKEVEN_*`、`DAILY_OPEN_ALERT_THRESHOLD`,以及 **`BINANCE_*`**(密钥、`BINANCE_MARGIN_MODE`、`BINANCE_POSITION_MODE`、`BINANCE_TRIGGER_WORKING_TYPE` 等)。资金字段舍入端口径与 **`FUNDS_DECIMALS`** 一致。
|
||||
- 第 1 次:首次检测到突破的当次轮询(若已闭合 5m 满足条件)。
|
||||
- 第 2、3 次:仅按间隔推送(**不要求**价格仍在箱外)。
|
||||
- 第 3 次推送后:写入 `key_monitor_history`,`close_reason=**key_level_alert_done**`,从 `key_monitors` **删除**。
|
||||
|
||||
### 2.4 与箱体/收敛的区别
|
||||
|
||||
| 项目 | 阻力/支撑 | 箱体/收敛 |
|
||||
|------|-----------|-----------|
|
||||
| 方向 | 程序推断 | 人工选择 |
|
||||
| K 线根数 | 1 根闭合 5m | 2 根(突破 K + 确认 K) |
|
||||
| 提醒次数 | 3 次后结案 | 自动单:触发后 1 次业务推送并结案 |
|
||||
|
||||
---
|
||||
|
||||
## 计价与下单口径
|
||||
## 三、箱体突破 / 收敛突破(自动开仓)
|
||||
|
||||
| 用途 | 价格 |
|
||||
|------|------|
|
||||
| 企业微信展示、**与 RR 门槛比较的计划 RR** | 确认 K(第二根闭合 5m)收盘 **`E`** |
|
||||
| **实际开仓** | **市价**(`place_exchange_order`,与 `/add_order` 一致);成交价可能与 `E` **滑点** |
|
||||
| **以损定仓** | `calc_risk_fraction(direction, 当前市价, 止损)` + `RISK_PERCENT`(保证金等 **`FUNDS_DECIMALS`** 舍入,与 `/add_order` 一致) |
|
||||
### 3.1 K 线结构(默认索引)
|
||||
|
||||
- 开仓成功后:`order_monitors.monitor_type` 为 **关键位监控**;持仓卡片「来源」显示之。手动开仓为 **下单监控**。
|
||||
- 持仓列表中的 **盈亏比**:按 **实际成交价** 相对 SL/TP 重算,可与「按 `E` 算的计划 RR」略有偏差。
|
||||
- **本仓库止盈止损挂单**:开仓后由 **`_binance_place_tp_sl_orders`** 挂载(与手动一致:U 本位条件/Algo 类触发单;具体类型以 ccxt / 交易所为准)。
|
||||
| 角色 | 环境变量 | 默认 | 含义 |
|
||||
|------|----------|------|------|
|
||||
| 突破 K | `KEY_CONFIRM_BREAKOUT_BAR` | `-2` | 倒数第 2 根闭合 K |
|
||||
| 确认 K | `KEY_CONFIRM_BAR` | `-1` | 倒数第 1 根闭合 K |
|
||||
|
||||
---
|
||||
### 3.2 硬门控(须全部通过)
|
||||
|
||||
## 自动单止盈 / 止损(仅箱体突破、收敛突破)
|
||||
1. **有效突破(收盘越界)**
|
||||
- 多:`突破 K 收盘 > upper`
|
||||
- 空:`突破 K 收盘 < lower`
|
||||
|
||||
添加关键位时在页面选择 **止盈止损方案**(写入 `key_monitors.sl_tp_mode`)。确认 K 收盘 **E**,箱体高 **H = |upper − lower|`**。
|
||||
2. **突破越过幅度(仅下限)**
|
||||
- 多:`(突破 K 收盘 − upper) / upper × 100 > KEY_BREAKOUT_AMP_MIN_PCT`(默认 **0.03%**)
|
||||
- 空:`(lower − 突破 K 收盘) / lower × 100 >` 同上
|
||||
- **无上限**;突破过猛由 **计划 RR** 过滤。
|
||||
- **不再**使用 K 线实体占开盘价比例;`KEY_BREAKOUT_AMP_MAX_PCT` **已不参与门控**。
|
||||
|
||||
3. **确认 K 不进箱体**
|
||||
- 多:确认 K 收盘 **`> upper`**(不得在 `[lower, upper]` 内)
|
||||
- 空:确认 K 收盘 **`< lower`**
|
||||
|
||||
4. **量能:** 突破 K 成交量 > 前 `KEY_VOLUME_MA_BARS`(默认 20)根均量 × `KEY_VOLUME_RATIO_MIN`(默认 1.3)
|
||||
|
||||
5. **日成交量排名:** 运行时仍须前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)
|
||||
|
||||
6. **计划 RR(最后经济门控):** 按确认 K 收盘 **E** 计算 SL/TP 后,`RR` **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)才市价开仓
|
||||
|
||||
### 3.3 止损 / 止盈(确认 K 收盘为 E)
|
||||
|
||||
箱体高 **H = |upper − lower|**。止损锚在 **突破 K 极值** 外侧:
|
||||
|
||||
| 方向 | 止损(标准/趋势方案) |
|
||||
|------|------------------------|
|
||||
| 多 | 突破 K **最低价** × (1 − `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
| 空 | 突破 K **最高价** × (1 + `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
|
||||
止盈方案见下表(与改版前一致):
|
||||
|
||||
| 方案 | `sl_tp_mode` | 多:SL / TP | 空:SL / TP |
|
||||
|------|--------------|-------------|-------------|
|
||||
| 标准突破(默认) | `standard` | 突破 K 低 × (1−`KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) / **E+H** | 突破 K 高 × (1+外侧%) / **E−H** |
|
||||
| 箱体1R·止盈1.5H | `box_1p5` | **E−H** / **E+1.5×H**(RR≈1.5) | **E+H** / **E−1.5×H** |
|
||||
| 趋势单·自填止盈 | `trend_manual` | 突破 K 低 × (1−`KEY_TREND_STOP_OUTSIDE_PCT`%) / **录入止盈** | 突破 K 高 × (1+外侧%) / **录入止盈** |
|
||||
| 标准突破 | `standard` | 突破 K 低外侧% / **E+H** | 突破 K 高外侧% / **E−H** |
|
||||
| 箱体 1R·止盈 1.5H | `box_1p5` | **E−H** / **E+1.5×H** | **E+H** / **E−1.5×H** |
|
||||
| 趋势单·自填止盈 | `trend_manual` | 突破 K 低 × (1−`KEY_TREND_STOP_OUTSIDE_PCT`%) / **录入止盈** | 突破 K 高外侧% / **录入止盈** |
|
||||
|
||||
计划 **`RR = calc_rr_ratio(direction, E, SL, TP)`**。若为 `None` 或 **RR ≤ `KEY_AUTO_MIN_PLANNED_RR`** → **不下单**,走 `rr_insufficient` 结案。
|
||||
|
||||
**移动保本:** 添加时可勾选(默认关);开仓写入 `order_monitors.breakeven_enabled` 与勾选一致。详见仓库根目录 `关键位止盈止损与移动保本更新说明.md`。
|
||||
|
||||
---
|
||||
|
||||
## 一次性结案(`close_reason`)
|
||||
|
||||
以下任一发生:**按需发微信** → **`key_monitor_history`** → **从 `key_monitors` 删除**;**不会对同一条关键位重复轮询重试开仓**。
|
||||
### 3.4 一次性结案(`close_reason`)
|
||||
|
||||
| `close_reason` | 含义 |
|
||||
|----------------|------|
|
||||
| `rr_insufficient` | 门控通过,但计划 RR 未达标或 SL/TP / RR **几何无效** |
|
||||
| `exchange_failed` | 计划 RR 达标,但未开实盘、`LIVE_TRADING_ENABLED=false`、风控、保证金或 **交易所报错** 等导致 **开仓失败** |
|
||||
| `auto_opened` | 计划 RR 达标且 **市价开仓成功**(已写 `order_monitors`,并已挂止盈止损) |
|
||||
| `key_level_alert_only` | 阻力/支撑位 **仅推送**结案 |
|
||||
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
|
||||
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
|
||||
| `auto_opened` | RR 达标且市价开仓成功 |
|
||||
| `key_level_alert_done` | 阻力/支撑 **3 次提醒** 完成 |
|
||||
|
||||
---
|
||||
|
||||
## 与企业微信推送
|
||||
## 四、环境与参数(`.env` 摘要)
|
||||
|
||||
每种结案路径 **至多一条**主业务推送(RR 不足 / 下单失败 / 开仓成功 / 阻力支撑仅提醒)。
|
||||
|
||||
旧版「满 `KEY_ALERT_MAX_TIMES` 次再归档」对已触发结案的路径 **不再适用**;表中 `notification_count`、`max_notify` 等字段仍可能存在,以 **导出、兼容** 为主。
|
||||
| 变量 | 箱体/收敛 | 阻力/支撑 |
|
||||
|------|-----------|-----------|
|
||||
| `KEY_BREAKOUT_AMP_MIN_PCT` | 突破越过下限(默认 0.03) | 不用 |
|
||||
| `KEY_BREAKOUT_AMP_MAX_PCT` | **已废弃门控** | 不用 |
|
||||
| `KEY_VOLUME_*` / `KEY_CONFIRM_*` | 用 | 不用 |
|
||||
| `KEY_AUTO_MIN_PLANNED_RR` | 用 | 不用 |
|
||||
| `KEY_ALERT_MAX_TIMES` / `KEY_ALERT_INTERVAL_MINUTES` | 不用 | 用(默认 3 次 / 5 分钟) |
|
||||
| `KEY_DAILY_VOLUME_RANK_MAX` | 添加时 + 运行时 | **仅添加时** |
|
||||
|
||||
---
|
||||
|
||||
## 相关代码位置(通用)
|
||||
## 五、相关代码
|
||||
|
||||
| 说明 | 符号 |
|
||||
| 说明 | 位置 |
|
||||
|------|------|
|
||||
| 门控与主循环 | `check_key_monitors` |
|
||||
| 录入、有仓拦截、4h Flash | `add_key` |
|
||||
| 市价开仓 + 写 `order_monitors` | `_market_open_for_key_monitor` |
|
||||
| 计划 RR | `calc_rr_ratio(direction, E, SL, TP)` |
|
||||
| 价格精度 | `round_price_to_exchange` |
|
||||
| 共享判定 | `key_monitor_lib.py` |
|
||||
| 主循环 | `check_key_monitors` |
|
||||
| 自动门控 | `_key_hard_checks` |
|
||||
| 阻力/支撑提醒 | `_process_key_rs_level_alert` |
|
||||
| 录入 | `add_key` |
|
||||
| 开仓 | `_market_open_for_key_monitor` |
|
||||
|
||||
Reference in New Issue
Block a user