feat: KEY_AUTO_ORDER_ENABLED 三所统一开关,联动关键位自动单与开仓类型
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -93,6 +93,22 @@ BINANCE_TRIGGER_WORKING_TYPE=CONTRACT_PRICE
|
||||
# 平仓盈亏估算:false=按仓位历史口径(已实现盈亏+手续费,不含资金费);true=含资金费
|
||||
# BINANCE_PNL_INCLUDE_FUNDING=false
|
||||
|
||||
# =============================================================================
|
||||
# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)
|
||||
# =============================================================================
|
||||
# 默认 false = 关闭所有关键位程序自动单(箱体/收敛/斐波/假突破/触价)
|
||||
#
|
||||
# POSITION_SIZING_MODE=risk(以损定仓)
|
||||
# false → 不执行任何关键位自动单;支撑/阻力提醒、人工下单、顺势加仓不受影响
|
||||
# true → 允许关键位全套自动(含触价)
|
||||
#
|
||||
# POSITION_SIZING_MODE=full_margin(全仓杠杆,须无仓切换)
|
||||
# false → 不执行触价自动单
|
||||
# true → 仅回调/突破触价可程序自动开仓;箱体/斐波等仍禁止
|
||||
#
|
||||
# 顺势加仓、趋势回调不受本开关控制;全仓模式下策略自动仍禁止。
|
||||
KEY_AUTO_ORDER_ENABLED=false
|
||||
|
||||
# =============================================================================
|
||||
# 关键位门控(页面「关键位监控」规则条与 _key_hard_checks 共用)
|
||||
# =============================================================================
|
||||
|
||||
@@ -176,6 +176,12 @@ from lib.trade.trade_policy_app_lib import (
|
||||
default_symbol_for_policy,
|
||||
trade_policy_template_context,
|
||||
)
|
||||
from lib.key_monitor.key_auto_order_lib import (
|
||||
check_monitor_type_add_allowed,
|
||||
effective_entry_reason_options,
|
||||
effective_stats_segment_defs,
|
||||
load_key_auto_order_enabled,
|
||||
)
|
||||
from lib.key_monitor.key_monitor_full_margin_lib import (
|
||||
monitor_type_disallowed_in_full_margin,
|
||||
purge_disallowed_key_monitors,
|
||||
@@ -363,6 +369,7 @@ FORCE_CLOSE_BJ_HOUR = int(os.getenv("FORCE_CLOSE_BJ_HOUR", "0"))
|
||||
AUTO_TRANSFER_BJ_HOUR = int(os.getenv("AUTO_TRANSFER_BJ_HOUR", "8"))
|
||||
# 计仓模式:risk=以损定仓(默认);full_margin=合约可用保证金×比例全仓杠杆(仅 env 切换,须无仓)
|
||||
POSITION_SIZING_MODE = load_position_sizing_mode()
|
||||
KEY_AUTO_ORDER_ENABLED = load_key_auto_order_enabled()
|
||||
TRADE_POLICY = load_trade_policy()
|
||||
WECHAT_TIMEOUT_SECONDS = int(os.getenv("WECHAT_TIMEOUT_SECONDS", "10"))
|
||||
AI_TIMEOUT_SECONDS = int(os.getenv("AI_TIMEOUT_SECONDS", "120"))
|
||||
@@ -1842,7 +1849,10 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
||||
return dm, wm, mm
|
||||
|
||||
segments = []
|
||||
for seg_key, seg_title, _meta in STATS_SEGMENT_DEFS:
|
||||
seg_defs = effective_stats_segment_defs(
|
||||
STATS_SEGMENT_DEFS, POSITION_SIZING_MODE, KEY_AUTO_ORDER_ENABLED
|
||||
)
|
||||
for seg_key, seg_title, _meta in seg_defs:
|
||||
dm, wm, mm = slice_metrics(seg_key)
|
||||
segments.append({"key": seg_key, "title": seg_title, "day": dm, "week": wm, "month": mm})
|
||||
|
||||
@@ -5919,6 +5929,8 @@ def _execute_trigger_entry_cross(conn, row):
|
||||
|
||||
|
||||
def check_trigger_entry_key_monitors():
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
return
|
||||
conn = get_db()
|
||||
placeholders = ",".join("?" * len(TRIGGER_ENTRY_MONITOR_TYPES))
|
||||
rows = conn.execute(
|
||||
@@ -5983,6 +5995,8 @@ def check_trigger_entry_key_monitors():
|
||||
|
||||
|
||||
def check_fib_key_monitors():
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
return
|
||||
conn = get_db()
|
||||
rows = conn.execute("SELECT * FROM key_monitors").fetchall()
|
||||
for r in rows:
|
||||
@@ -6217,6 +6231,9 @@ def check_key_monitors():
|
||||
print(f"[key_rs_level_alert] {sym} id={r['id']}: {e}")
|
||||
continue
|
||||
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
continue
|
||||
|
||||
direction = (r["direction"] or "long").lower()
|
||||
if direction == KEY_DIRECTION_WATCH:
|
||||
continue
|
||||
@@ -7239,8 +7256,15 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
breakeven_offset_pct=BREAKEVEN_OFFSET_PCT,
|
||||
price_fmt=format_price_for_symbol,
|
||||
funds_fmt=format_funds_u,
|
||||
entry_reason_options=list(ENTRY_REASON_OPTIONS),
|
||||
entry_reason_options=list(
|
||||
effective_entry_reason_options(
|
||||
ENTRY_REASON_OPTIONS,
|
||||
POSITION_SIZING_MODE,
|
||||
KEY_AUTO_ORDER_ENABLED,
|
||||
)
|
||||
),
|
||||
entry_reason_other_value=ENTRY_REASON_OTHER,
|
||||
key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED,
|
||||
journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES,
|
||||
journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1,
|
||||
journal_chart_default_tf2=JOURNAL_CHART_DEFAULT_TF2,
|
||||
@@ -8108,11 +8132,11 @@ def add_key():
|
||||
if mt not in allowed_types:
|
||||
flash("监控类型无效")
|
||||
return redirect("/key_monitor")
|
||||
if is_full_margin_mode(POSITION_SIZING_MODE) and monitor_type_disallowed_in_full_margin(mt):
|
||||
flash(
|
||||
"全仓杠杆模式下不可添加箱体/收敛突破、斐波或假突破监控;"
|
||||
"可使用「回调/突破触价开仓」或阻力/支撑(仅提醒),或切换 POSITION_SIZING_MODE=risk 并重启(须无持仓)。"
|
||||
)
|
||||
ok_mt, mt_msg = check_monitor_type_add_allowed(
|
||||
mt, POSITION_SIZING_MODE, KEY_AUTO_ORDER_ENABLED
|
||||
)
|
||||
if not ok_mt:
|
||||
flash(mt_msg)
|
||||
return redirect("/key_monitor")
|
||||
skip_volume_rank = is_false_breakout_key_monitor_type(mt)
|
||||
rank, total = None, None
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
# 关键位监控说明(自动开仓 + 人工盯盘)
|
||||
|
||||
**适用:`crypto_monitor_gate`(Gate U 本位永续)**
|
||||
Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`。
|
||||
**适用:Gate / Binance / OKX 三所实例(共用 `lib/key_monitor/key_auto_order_lib.py`)**
|
||||
|
||||
## 环境开关 `KEY_AUTO_ORDER_ENABLED`(默认 `false`)
|
||||
|
||||
| 计仓模式 | 开关 | 关键位程序自动单 |
|
||||
|----------|------|------------------|
|
||||
| `risk`(以损定仓) | `false` | **全部关闭**(含触价);支撑/阻力微信提醒仍可用 |
|
||||
| `risk` | `true` | 箱体/收敛/斐波/假突破/触价均可自动(旧行为) |
|
||||
| `full_margin`(全仓) | `false` | 全部关闭(含触价) |
|
||||
| `full_margin` | `true` | **仅触价**自动;箱体/斐波等仍禁止 |
|
||||
|
||||
**不受本开关影响:** 人工实盘下单、关键支撑/阻力提醒、**顺势加仓**(`risk` 下)、趋势回调(`risk` 下)。全仓模式下策略自动仍禁止。
|
||||
|
||||
修改 `.env` 后须 **重启 PM2**。复盘「开仓类型」与统计分段会随开关联动隐藏关键位选项。
|
||||
|
||||
---
|
||||
|
||||
**适用:`crypto_monitor_binance`(Binance U 本位)**
|
||||
Gate / OKX 见各自目录下同名文档;共享逻辑在 `lib/key_monitor/`。
|
||||
|
||||
本文档与 `.env`、`check_key_monitors`、`add_key`、`_key_hard_checks`、`_process_key_rs_level_alert` 一致。
|
||||
|
||||
|
||||
@@ -93,6 +93,22 @@ GATE_TPSL_LAST_PRICE_GAP_PCT=0.05
|
||||
# 页面与浏览器标签展示的交易所名称(多环境区分时可改成例如 Gate·模拟)
|
||||
# EXCHANGE_DISPLAY_NAME=Gate.io
|
||||
|
||||
# =============================================================================
|
||||
# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)
|
||||
# =============================================================================
|
||||
# 默认 false = 关闭所有关键位程序自动单(箱体/收敛/斐波/假突破/触价)
|
||||
#
|
||||
# POSITION_SIZING_MODE=risk(以损定仓)
|
||||
# false → 不执行任何关键位自动单;支撑/阻力提醒、人工下单、顺势加仓不受影响
|
||||
# true → 允许关键位全套自动(含触价)
|
||||
#
|
||||
# POSITION_SIZING_MODE=full_margin(全仓杠杆,须无仓切换)
|
||||
# false → 不执行触价自动单
|
||||
# true → 仅回调/突破触价可程序自动开仓;箱体/斐波等仍禁止
|
||||
#
|
||||
# 顺势加仓、趋势回调不受本开关控制;全仓模式下策略自动仍禁止。
|
||||
KEY_AUTO_ORDER_ENABLED=false
|
||||
|
||||
# =============================================================================
|
||||
# 关键位门控(页面「关键位监控」规则条与 _key_hard_checks 共用)
|
||||
# =============================================================================
|
||||
|
||||
@@ -175,6 +175,12 @@ from lib.trade.trade_policy_app_lib import (
|
||||
default_symbol_for_policy,
|
||||
trade_policy_template_context,
|
||||
)
|
||||
from lib.key_monitor.key_auto_order_lib import (
|
||||
check_monitor_type_add_allowed,
|
||||
effective_entry_reason_options,
|
||||
effective_stats_segment_defs,
|
||||
load_key_auto_order_enabled,
|
||||
)
|
||||
from lib.key_monitor.key_monitor_full_margin_lib import (
|
||||
monitor_type_disallowed_in_full_margin,
|
||||
purge_disallowed_key_monitors,
|
||||
@@ -353,6 +359,7 @@ FORCE_CLOSE_BJ_HOUR = int(os.getenv("FORCE_CLOSE_BJ_HOUR", "0"))
|
||||
# 自动划转:仅在北京时间该整点「小时」内尝试;transfer_logs.transfer_day 存 UTC 自然日便于对账
|
||||
AUTO_TRANSFER_BJ_HOUR = int(os.getenv("AUTO_TRANSFER_BJ_HOUR", "8"))
|
||||
POSITION_SIZING_MODE = load_position_sizing_mode()
|
||||
KEY_AUTO_ORDER_ENABLED = load_key_auto_order_enabled()
|
||||
TRADE_POLICY = load_trade_policy()
|
||||
WECHAT_TIMEOUT_SECONDS = int(os.getenv("WECHAT_TIMEOUT_SECONDS", "10"))
|
||||
AI_TIMEOUT_SECONDS = int(os.getenv("AI_TIMEOUT_SECONDS", "120"))
|
||||
@@ -1839,7 +1846,10 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
||||
return dm, wm, mm
|
||||
|
||||
segments = []
|
||||
for seg_key, seg_title, _meta in STATS_SEGMENT_DEFS:
|
||||
seg_defs = effective_stats_segment_defs(
|
||||
STATS_SEGMENT_DEFS, POSITION_SIZING_MODE, KEY_AUTO_ORDER_ENABLED
|
||||
)
|
||||
for seg_key, seg_title, _meta in seg_defs:
|
||||
dm, wm, mm = slice_metrics(seg_key)
|
||||
segments.append({"key": seg_key, "title": seg_title, "day": dm, "week": wm, "month": mm})
|
||||
|
||||
@@ -5560,6 +5570,8 @@ def _execute_trigger_entry_cross(conn, row):
|
||||
|
||||
|
||||
def check_trigger_entry_key_monitors():
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
return
|
||||
conn = get_db()
|
||||
placeholders = ",".join("?" * len(TRIGGER_ENTRY_MONITOR_TYPES))
|
||||
rows = conn.execute(
|
||||
@@ -5624,6 +5636,8 @@ def check_trigger_entry_key_monitors():
|
||||
|
||||
|
||||
def check_fib_key_monitors():
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
return
|
||||
conn = get_db()
|
||||
rows = conn.execute("SELECT * FROM key_monitors").fetchall()
|
||||
for r in rows:
|
||||
@@ -5858,6 +5872,9 @@ def check_key_monitors():
|
||||
print(f"[key_rs_level_alert] {sym} id={r['id']}: {e}")
|
||||
continue
|
||||
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
continue
|
||||
|
||||
direction = (r["direction"] or "long").lower()
|
||||
if direction == KEY_DIRECTION_WATCH:
|
||||
continue
|
||||
@@ -7018,8 +7035,15 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
funds_fmt=format_usdt,
|
||||
usdt_fmt=format_usdt,
|
||||
signed_usdt_fmt=format_signed_usdt,
|
||||
entry_reason_options=list(ENTRY_REASON_OPTIONS),
|
||||
entry_reason_options=list(
|
||||
effective_entry_reason_options(
|
||||
ENTRY_REASON_OPTIONS,
|
||||
POSITION_SIZING_MODE,
|
||||
KEY_AUTO_ORDER_ENABLED,
|
||||
)
|
||||
),
|
||||
entry_reason_other_value=ENTRY_REASON_OTHER,
|
||||
key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED,
|
||||
journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES,
|
||||
journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1,
|
||||
journal_chart_default_tf2=JOURNAL_CHART_DEFAULT_TF2,
|
||||
@@ -7905,11 +7929,11 @@ def add_key():
|
||||
if mt not in allowed_types:
|
||||
flash("监控类型无效")
|
||||
return redirect("/key_monitor")
|
||||
if is_full_margin_mode(POSITION_SIZING_MODE) and monitor_type_disallowed_in_full_margin(mt):
|
||||
flash(
|
||||
"全仓杠杆模式下不可添加箱体/收敛突破、斐波或假突破监控;"
|
||||
"可使用「回调/突破触价开仓」或阻力/支撑(仅提醒),或切换 POSITION_SIZING_MODE=risk 并重启(须无持仓)。"
|
||||
)
|
||||
ok_mt, mt_msg = check_monitor_type_add_allowed(
|
||||
mt, POSITION_SIZING_MODE, KEY_AUTO_ORDER_ENABLED
|
||||
)
|
||||
if not ok_mt:
|
||||
flash(mt_msg)
|
||||
return redirect("/key_monitor")
|
||||
skip_volume_rank = is_false_breakout_key_monitor_type(mt)
|
||||
rank, total = None, None
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
# 关键位监控说明(自动开仓 + 人工盯盘)
|
||||
|
||||
**适用:Gate / Binance / OKX 三所实例(共用 `lib/key_monitor/key_auto_order_lib.py`)**
|
||||
|
||||
## 环境开关 `KEY_AUTO_ORDER_ENABLED`(默认 `false`)
|
||||
|
||||
| 计仓模式 | 开关 | 关键位程序自动单 |
|
||||
|----------|------|------------------|
|
||||
| `risk`(以损定仓) | `false` | **全部关闭**(含触价);支撑/阻力微信提醒仍可用 |
|
||||
| `risk` | `true` | 箱体/收敛/斐波/假突破/触价均可自动(旧行为) |
|
||||
| `full_margin`(全仓) | `false` | 全部关闭(含触价) |
|
||||
| `full_margin` | `true` | **仅触价**自动;箱体/斐波等仍禁止 |
|
||||
|
||||
**不受本开关影响:** 人工实盘下单、关键支撑/阻力提醒、**顺势加仓**(`risk` 下)、趋势回调(`risk` 下)。全仓模式下策略自动仍禁止。
|
||||
|
||||
修改 `.env` 后须 **重启 PM2**。复盘「开仓类型」与统计分段会随开关联动隐藏关键位选项。
|
||||
|
||||
---
|
||||
|
||||
**适用:`crypto_monitor_gate`(Gate U 本位永续)**
|
||||
Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`。
|
||||
Binance / OKX 见各自目录下同名文档;共享逻辑在 `lib/key_monitor/`。
|
||||
|
||||
本文档与 `.env`、`check_key_monitors`、`add_key`、`_key_hard_checks`、`_process_key_rs_level_alert` 一致。
|
||||
|
||||
|
||||
@@ -89,6 +89,22 @@ OKX_POSITION_INST_TYPE=SWAP
|
||||
# 单次拉取历史仓位条数上限(OKX 每页最多 100,程序会分页)
|
||||
# EXCHANGE_POSITION_HISTORY_LIMIT=200
|
||||
|
||||
# =============================================================================
|
||||
# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)
|
||||
# =============================================================================
|
||||
# 默认 false = 关闭所有关键位程序自动单(箱体/收敛/斐波/假突破/触价)
|
||||
#
|
||||
# POSITION_SIZING_MODE=risk(以损定仓)
|
||||
# false → 不执行任何关键位自动单;支撑/阻力提醒、人工下单、顺势加仓不受影响
|
||||
# true → 允许关键位全套自动(含触价)
|
||||
#
|
||||
# POSITION_SIZING_MODE=full_margin(全仓杠杆,须无仓切换)
|
||||
# false → 不执行触价自动单
|
||||
# true → 仅回调/突破触价可程序自动开仓;箱体/斐波等仍禁止
|
||||
#
|
||||
# 顺势加仓、趋势回调不受本开关控制;全仓模式下策略自动仍禁止。
|
||||
KEY_AUTO_ORDER_ENABLED=false
|
||||
|
||||
# 关键位监控:5m收线突破过滤参数
|
||||
KLINE_TIMEFRAME=5m
|
||||
KEY_BREAKOUT_LIMIT_PCT=1.5
|
||||
|
||||
@@ -174,6 +174,12 @@ from lib.trade.trade_policy_app_lib import (
|
||||
default_symbol_for_policy,
|
||||
trade_policy_template_context,
|
||||
)
|
||||
from lib.key_monitor.key_auto_order_lib import (
|
||||
check_monitor_type_add_allowed,
|
||||
effective_entry_reason_options,
|
||||
effective_stats_segment_defs,
|
||||
load_key_auto_order_enabled,
|
||||
)
|
||||
from lib.key_monitor.key_monitor_full_margin_lib import (
|
||||
monitor_type_disallowed_in_full_margin,
|
||||
purge_disallowed_key_monitors,
|
||||
@@ -325,6 +331,7 @@ FORCE_CLOSE_BJ_HOUR = int(os.getenv("FORCE_CLOSE_BJ_HOUR", "0"))
|
||||
# 自动划转:仅在北京时间该整点「小时」内尝试;transfer_logs.transfer_day 存 UTC 自然日(与 OKX 日界一致便于对账)
|
||||
AUTO_TRANSFER_BJ_HOUR = int(os.getenv("AUTO_TRANSFER_BJ_HOUR", "8"))
|
||||
POSITION_SIZING_MODE = load_position_sizing_mode()
|
||||
KEY_AUTO_ORDER_ENABLED = load_key_auto_order_enabled()
|
||||
TRADE_POLICY = load_trade_policy()
|
||||
WECHAT_TIMEOUT_SECONDS = int(os.getenv("WECHAT_TIMEOUT_SECONDS", "10"))
|
||||
AI_TIMEOUT_SECONDS = int(os.getenv("AI_TIMEOUT_SECONDS", "120"))
|
||||
@@ -1815,7 +1822,10 @@ def compute_stats_bundle(conn, trading_day, now_dt=None):
|
||||
return dm, wm, mm
|
||||
|
||||
segments = []
|
||||
for seg_key, seg_title, _meta in STATS_SEGMENT_DEFS:
|
||||
seg_defs = effective_stats_segment_defs(
|
||||
STATS_SEGMENT_DEFS, POSITION_SIZING_MODE, KEY_AUTO_ORDER_ENABLED
|
||||
)
|
||||
for seg_key, seg_title, _meta in seg_defs:
|
||||
dm, wm, mm = slice_metrics(seg_key)
|
||||
segments.append({"key": seg_key, "title": seg_title, "day": dm, "week": wm, "month": mm})
|
||||
|
||||
@@ -5135,6 +5145,8 @@ def _execute_trigger_entry_cross(conn, row):
|
||||
|
||||
|
||||
def check_trigger_entry_key_monitors():
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
return
|
||||
conn = get_db()
|
||||
placeholders = ",".join("?" * len(TRIGGER_ENTRY_MONITOR_TYPES))
|
||||
rows = conn.execute(
|
||||
@@ -5199,6 +5211,8 @@ def check_trigger_entry_key_monitors():
|
||||
|
||||
|
||||
def check_fib_key_monitors():
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
return
|
||||
conn = get_db()
|
||||
rows = conn.execute("SELECT * FROM key_monitors").fetchall()
|
||||
for r in rows:
|
||||
@@ -5672,6 +5686,9 @@ def check_key_monitors():
|
||||
print(f"[key_rs_level_alert] {sym} id={r['id']}: {e}")
|
||||
continue
|
||||
|
||||
if not KEY_AUTO_ORDER_ENABLED:
|
||||
continue
|
||||
|
||||
direction = (r["direction"] or "long").lower()
|
||||
if direction == KEY_DIRECTION_WATCH:
|
||||
continue
|
||||
@@ -6576,8 +6593,15 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
breakeven_rr_trigger=BREAKEVEN_RR_TRIGGER,
|
||||
breakeven_offset_pct=BREAKEVEN_OFFSET_PCT,
|
||||
price_fmt=format_price_for_symbol,
|
||||
entry_reason_options=list(ENTRY_REASON_OPTIONS),
|
||||
entry_reason_options=list(
|
||||
effective_entry_reason_options(
|
||||
ENTRY_REASON_OPTIONS,
|
||||
POSITION_SIZING_MODE,
|
||||
KEY_AUTO_ORDER_ENABLED,
|
||||
)
|
||||
),
|
||||
entry_reason_other_value=ENTRY_REASON_OTHER,
|
||||
key_auto_order_enabled=KEY_AUTO_ORDER_ENABLED,
|
||||
journal_chart_tf_choices=JOURNAL_CHART_TF_CHOICES,
|
||||
journal_chart_default_tf1=JOURNAL_CHART_DEFAULT_TF1,
|
||||
journal_chart_default_tf2=JOURNAL_CHART_DEFAULT_TF2,
|
||||
@@ -7512,11 +7536,11 @@ def add_key():
|
||||
if mt not in allowed_types:
|
||||
flash("监控类型无效")
|
||||
return redirect("/key_monitor")
|
||||
if is_full_margin_mode(POSITION_SIZING_MODE) and monitor_type_disallowed_in_full_margin(mt):
|
||||
flash(
|
||||
"全仓杠杆模式下不可添加箱体/收敛突破、斐波或假突破监控;"
|
||||
"可使用「回调/突破触价开仓」或阻力/支撑(仅提醒),或切换 POSITION_SIZING_MODE=risk 并重启(须无持仓)。"
|
||||
)
|
||||
ok_mt, mt_msg = check_monitor_type_add_allowed(
|
||||
mt, POSITION_SIZING_MODE, KEY_AUTO_ORDER_ENABLED
|
||||
)
|
||||
if not ok_mt:
|
||||
flash(mt_msg)
|
||||
return redirect("/key_monitor")
|
||||
skip_volume_rank = is_false_breakout_key_monitor_type(mt)
|
||||
rank, total = None, None
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
# 关键位监控说明(自动开仓 + 人工盯盘)
|
||||
|
||||
**适用:Gate / Binance / OKX 三所实例(共用 `lib/key_monitor/key_auto_order_lib.py`)**
|
||||
|
||||
## 环境开关 `KEY_AUTO_ORDER_ENABLED`(默认 `false`)
|
||||
|
||||
| 计仓模式 | 开关 | 关键位程序自动单 |
|
||||
|----------|------|------------------|
|
||||
| `risk`(以损定仓) | `false` | **全部关闭**(含触价);支撑/阻力微信提醒仍可用 |
|
||||
| `risk` | `true` | 箱体/收敛/斐波/假突破/触价均可自动(旧行为) |
|
||||
| `full_margin`(全仓) | `false` | 全部关闭(含触价) |
|
||||
| `full_margin` | `true` | **仅触价**自动;箱体/斐波等仍禁止 |
|
||||
|
||||
**不受本开关影响:** 人工实盘下单、关键支撑/阻力提醒、**顺势加仓**(`risk` 下)、趋势回调(`risk` 下)。全仓模式下策略自动仍禁止。
|
||||
|
||||
修改 `.env` 后须 **重启 PM2**。复盘「开仓类型」与统计分段会随开关联动隐藏关键位选项。
|
||||
|
||||
---
|
||||
|
||||
**适用:`crypto_monitor_gate`(Gate U 本位永续)**
|
||||
Binance / OKX 见各自目录下同名文档;共享逻辑在仓库根目录 `key_monitor_lib.py`。
|
||||
Binance / OKX 见各自目录下同名文档;共享逻辑在 `lib/key_monitor/`。
|
||||
|
||||
本文档与 `.env`、`check_key_monitors`、`add_key`、`_key_hard_checks`、`_process_key_rs_level_alert` 一致。
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
# full_margin = 全仓杠杆(合约可用保证金 × 比例)
|
||||
POSITION_SIZING_MODE=risk
|
||||
FULL_MARGIN_BUFFER_RATIO=0.98
|
||||
|
||||
# 关键位程序自动单(默认 false,详见各所 关键位自动下单说明.md)
|
||||
KEY_AUTO_ORDER_ENABLED=false
|
||||
```
|
||||
|
||||
切换为全仓杠杆前:**交易所须无持仓**(`MAX_ACTIVE_POSITIONS` 默认 1,全仓模式会强制单仓)。
|
||||
@@ -17,11 +20,13 @@ FULL_MARGIN_BUFFER_RATIO=0.98
|
||||
|
||||
| 模式 | 保证金计算 | 杠杆 | 允许入口 |
|
||||
|------|------------|------|----------|
|
||||
| `risk` | `RISK_PERCENT` × 交易资金,按止损距离反推 | 表单可选 / 同步交易所 | 实盘人工、关键位自动、趋势回调、顺势加仓 |
|
||||
| `full_margin` | **合约账户可用 USDT × `FULL_MARGIN_BUFFER_RATIO`**(保留 2 位小数) | BTC/ETH **10x**,其它 **5x**(与 `BTC_LEVERAGE`/`ALT_LEVERAGE` 一致) | **实盘人工下单**、**关键位触价开仓**;阻力/支撑仅提醒 |
|
||||
| `risk` | `RISK_PERCENT` × 交易资金,按止损距离反推 | 表单可选 / 同步交易所 | 实盘人工、关键位自动(须 `KEY_AUTO_ORDER_ENABLED=true`)、趋势回调、顺势加仓 |
|
||||
| `full_margin` | **合约账户可用 USDT × `FULL_MARGIN_BUFFER_RATIO`**(保留 2 位小数) | BTC/ETH **10x**,其它 **5x**(与 `BTC_LEVERAGE`/`ALT_LEVERAGE` 一致) | **实盘人工下单**、**关键位触价**(须 `KEY_AUTO_ORDER_ENABLED=true`);阻力/支撑仅提醒 |
|
||||
|
||||
全仓模式下:
|
||||
|
||||
- **`KEY_AUTO_ORDER_ENABLED=false`(默认)** 时,触价程序自动单也不执行。
|
||||
- **`KEY_AUTO_ORDER_ENABLED=true`** 时,仅触价可程序自动开仓;箱体/斐波等仍禁止。
|
||||
- 仍校验 **计划盈亏比**(实盘用 `MANUAL_MIN_PLANNED_RR`;触价开仓用 `KEY_AUTO_MIN_PLANNED_RR`)。
|
||||
- 下单张数由 `prepare_order_amount` + 交易所 `amount_to_precision` 决定。
|
||||
- `order_monitors.initial_stop_loss` 仍记录**开仓时**止损快照;交易记录复盘以该快照为准。
|
||||
@@ -32,7 +37,18 @@ FULL_MARGIN_BUFFER_RATIO=0.98
|
||||
- 关键位:箱体突破、收敛突破、斐波、假突破(添加时拒绝;已存在则启动时撤销)。
|
||||
- 趋势回调、顺势加仓(策略入口返回明确错误)。
|
||||
|
||||
**允许:** 关键位 **回调触价开仓** / **突破触价开仓**(程序盯价、触达/穿越计划入场后市价成交,无交易所挂单;全仓下仅允许一条待触发)。
|
||||
**允许(须 `KEY_AUTO_ORDER_ENABLED=true`):** 关键位 **回调触价开仓** / **突破触价开仓**(程序盯价、触达/穿越计划入场后市价成交,无交易所挂单;全仓下仅允许一条待触发)。
|
||||
|
||||
## `KEY_AUTO_ORDER_ENABLED`(三所统一,默认 `false`)
|
||||
|
||||
| 计仓 | 开关 | 效果 |
|
||||
|------|------|------|
|
||||
| `risk` | `false` | 关闭全部关键位程序自动单(含触价);顺势加仓不受影响 |
|
||||
| `risk` | `true` | 关键位全套自动(旧行为) |
|
||||
| `full_margin` | `false` | 关闭触价自动 |
|
||||
| `full_margin` | `true` | 仅触价自动 |
|
||||
|
||||
详见各实例目录 `关键位自动下单说明.md`。
|
||||
|
||||
## 用脚本更新三所 `.env`
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
"""关键位程序自动下单开关(三所共用,与 POSITION_SIZING_MODE 联动)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Optional, Sequence, Tuple
|
||||
|
||||
from lib.key_monitor.fib_key_monitor_lib import is_fib_key_monitor_type
|
||||
from lib.key_monitor.false_breakout_key_monitor_lib import is_false_breakout_key_monitor_type
|
||||
from lib.key_monitor.key_monitor_full_margin_lib import monitor_type_disallowed_in_full_margin
|
||||
from lib.key_monitor.key_monitor_lib import KEY_MONITOR_AUTO_TYPES, KEY_MONITOR_RS_TYPES
|
||||
from lib.key_monitor.trigger_entry_key_monitor_lib import is_trigger_entry_key_monitor_type
|
||||
from lib.trade.position_sizing_lib import is_full_margin_mode
|
||||
|
||||
KEY_ENTRY_REASON_OPTIONS: Tuple[str, ...] = (
|
||||
"关键位箱体突破",
|
||||
"关键位收敛突破",
|
||||
"关键位斐波0.618",
|
||||
"关键位斐波0.786",
|
||||
"关键位假突破",
|
||||
"关键位回调触价开仓",
|
||||
"关键位突破触价开仓",
|
||||
)
|
||||
|
||||
KEY_ENTRY_REASON_TRIGGER_OPTIONS: frozenset[str] = frozenset(
|
||||
{
|
||||
"关键位回调触价开仓",
|
||||
"关键位突破触价开仓",
|
||||
}
|
||||
)
|
||||
|
||||
KEY_STATS_SEGMENT_KEYS: frozenset[str] = frozenset(
|
||||
{
|
||||
"key_box",
|
||||
"key_conv",
|
||||
"key_fib618",
|
||||
"key_fib786",
|
||||
"key_false_breakout",
|
||||
"key_trigger",
|
||||
}
|
||||
)
|
||||
|
||||
KEY_STATS_TRIGGER_ONLY: frozenset[str] = frozenset({"key_trigger"})
|
||||
|
||||
TREND_MANUAL_ENTRY_REASON_COUNT = 5
|
||||
|
||||
|
||||
def _env_bool(raw: Optional[str], default: bool = False) -> bool:
|
||||
if raw is None:
|
||||
return default
|
||||
return (raw or "").strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
|
||||
def load_key_auto_order_enabled(env: Optional[dict] = None) -> bool:
|
||||
e = env if env is not None else os.environ
|
||||
return _env_bool(e.get("KEY_AUTO_ORDER_ENABLED"), default=False)
|
||||
|
||||
|
||||
def is_key_level_entry_reason(reason: str) -> bool:
|
||||
return (reason or "").strip() in KEY_ENTRY_REASON_OPTIONS
|
||||
|
||||
|
||||
def visible_key_entry_reasons(sizing_mode: str, key_auto_enabled: bool) -> Tuple[str, ...]:
|
||||
if not key_auto_enabled:
|
||||
return ()
|
||||
if is_full_margin_mode(sizing_mode):
|
||||
return tuple(x for x in KEY_ENTRY_REASON_OPTIONS if x in KEY_ENTRY_REASON_TRIGGER_OPTIONS)
|
||||
return KEY_ENTRY_REASON_OPTIONS
|
||||
|
||||
|
||||
def effective_entry_reason_options(
|
||||
all_options: Sequence[str],
|
||||
sizing_mode: str,
|
||||
key_auto_enabled: bool,
|
||||
*,
|
||||
trend_manual_count: int = TREND_MANUAL_ENTRY_REASON_COUNT,
|
||||
) -> Tuple[str, ...]:
|
||||
"""复盘/表单下拉:按开关与计仓模式裁剪关键位开仓类型。"""
|
||||
opts = list(all_options)
|
||||
if len(opts) <= trend_manual_count:
|
||||
return tuple(opts)
|
||||
key_visible = set(visible_key_entry_reasons(sizing_mode, key_auto_enabled))
|
||||
out: list[str] = []
|
||||
for i, item in enumerate(opts):
|
||||
if i < trend_manual_count:
|
||||
out.append(item)
|
||||
elif is_key_level_entry_reason(item):
|
||||
if item in key_visible:
|
||||
out.append(item)
|
||||
else:
|
||||
out.append(item)
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def effective_stats_segment_defs(
|
||||
segment_defs: Sequence[Tuple[str, str, Any]],
|
||||
sizing_mode: str,
|
||||
key_auto_enabled: bool,
|
||||
) -> Tuple[Tuple[str, str, Any], ...]:
|
||||
if not key_auto_enabled:
|
||||
hidden = KEY_STATS_SEGMENT_KEYS
|
||||
elif is_full_margin_mode(sizing_mode):
|
||||
hidden = KEY_STATS_SEGMENT_KEYS - KEY_STATS_TRIGGER_ONLY
|
||||
else:
|
||||
hidden = frozenset()
|
||||
return tuple(x for x in segment_defs if x[0] not in hidden)
|
||||
|
||||
|
||||
def is_key_auto_monitor_type(monitor_type: str) -> bool:
|
||||
mt = (monitor_type or "").strip()
|
||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||
return True
|
||||
if is_fib_key_monitor_type(mt):
|
||||
return True
|
||||
if is_false_breakout_key_monitor_type(mt):
|
||||
return True
|
||||
if is_trigger_entry_key_monitor_type(mt):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_rs_key_monitor_type(monitor_type: str) -> bool:
|
||||
return (monitor_type or "").strip() in KEY_MONITOR_RS_TYPES
|
||||
|
||||
|
||||
def check_monitor_type_add_allowed(
|
||||
monitor_type: str,
|
||||
sizing_mode: str,
|
||||
key_auto_enabled: bool,
|
||||
) -> Tuple[bool, str]:
|
||||
mt = (monitor_type or "").strip()
|
||||
if is_rs_key_monitor_type(mt):
|
||||
return True, ""
|
||||
if not key_auto_enabled:
|
||||
return False, (
|
||||
"已关闭关键位程序自动单(KEY_AUTO_ORDER_ENABLED=false);"
|
||||
"仅可添加「关键支撑阻力」(微信提醒,不下单)。"
|
||||
)
|
||||
if is_full_margin_mode(sizing_mode):
|
||||
if monitor_type_disallowed_in_full_margin(mt):
|
||||
return False, (
|
||||
"全仓杠杆模式下不可添加箱体/收敛突破、斐波或假突破监控;"
|
||||
"可使用「回调/突破触价开仓」或关键支撑阻力(仅提醒)。"
|
||||
)
|
||||
if not is_key_auto_monitor_type(mt) and not is_rs_key_monitor_type(mt):
|
||||
return False, "监控类型无效"
|
||||
return True, ""
|
||||
|
||||
|
||||
def key_auto_order_env_comment_lines() -> Tuple[str, ...]:
|
||||
return (
|
||||
"# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)",
|
||||
"# 默认 false = 关闭所有关键位程序自动单(箱体/收敛/斐波/假突破/触价)",
|
||||
"#",
|
||||
"# POSITION_SIZING_MODE=risk(以损定仓)",
|
||||
"# false → 不执行任何关键位自动单;支撑/阻力提醒、人工下单、顺势加仓不受影响",
|
||||
"# true → 允许关键位全套自动(含触价)",
|
||||
"#",
|
||||
"# POSITION_SIZING_MODE=full_margin(全仓杠杆,须无仓切换)",
|
||||
"# false → 不执行触价自动单",
|
||||
"# true → 仅回调/突破触价可程序自动开仓;箱体/斐波等仍禁止",
|
||||
"#",
|
||||
"# 顺势加仓、趋势回调不受本开关控制;全仓模式下策略自动仍禁止。",
|
||||
"KEY_AUTO_ORDER_ENABLED=false",
|
||||
)
|
||||
@@ -145,15 +145,17 @@
|
||||
{% from 'trade_policy_fields.html' import trade_policy_symbol, trade_policy_direction with context %}
|
||||
{{ trade_policy_symbol('symbol', 'key-symbol') }}
|
||||
<select name="type" id="key-type-select" required>
|
||||
{% if position_sizing_mode != 'full_margin' %}
|
||||
{% if key_auto_order_enabled|default(false) and position_sizing_mode != 'full_margin' %}
|
||||
<option value="箱体突破">箱体突破</option>
|
||||
<option value="收敛突破">收敛突破</option>
|
||||
<option value="斐波回调0.618">斐波回调0.618</option>
|
||||
<option value="斐波回调0.786">斐波回调0.786</option>
|
||||
<option value="假突破">假突破(BTC/ETH)</option>
|
||||
{% endif %}
|
||||
{% if key_auto_order_enabled|default(false) %}
|
||||
<option value="回调触价开仓">回调触价开仓</option>
|
||||
<option value="突破触价开仓">突破触价开仓</option>
|
||||
{% endif %}
|
||||
<option value="关键支撑阻力">关键支撑阻力</option>
|
||||
</select>
|
||||
{{ trade_policy_direction('direction', 'key-direction') }}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""key_auto_order_lib 单元测试。"""
|
||||
import unittest
|
||||
|
||||
from lib.key_monitor.key_auto_order_lib import (
|
||||
check_monitor_type_add_allowed,
|
||||
effective_entry_reason_options,
|
||||
effective_stats_segment_defs,
|
||||
load_key_auto_order_enabled,
|
||||
)
|
||||
from lib.trade.position_sizing_lib import MODE_FULL_MARGIN, MODE_RISK
|
||||
|
||||
FULL_OPTS = (
|
||||
"趋势A",
|
||||
"趋势B",
|
||||
"趋势C",
|
||||
"趋势D",
|
||||
"趋势E",
|
||||
"关键位箱体突破",
|
||||
"关键位收敛突破",
|
||||
"关键位斐波0.618",
|
||||
"关键位斐波0.786",
|
||||
"关键位假突破",
|
||||
"关键位回调触价开仓",
|
||||
"关键位突破触价开仓",
|
||||
"趋势回调",
|
||||
"顺势加仓",
|
||||
)
|
||||
|
||||
STATS_DEFS = (
|
||||
("all", "全部", {}),
|
||||
("key_box", "箱体", {}),
|
||||
("key_trigger", "触价", {}),
|
||||
)
|
||||
|
||||
|
||||
class KeyAutoOrderLibTest(unittest.TestCase):
|
||||
def test_load_default_false(self):
|
||||
self.assertFalse(load_key_auto_order_enabled({"KEY_AUTO_ORDER_ENABLED": "false"}))
|
||||
self.assertFalse(load_key_auto_order_enabled({}))
|
||||
self.assertTrue(load_key_auto_order_enabled({"KEY_AUTO_ORDER_ENABLED": "true"}))
|
||||
|
||||
def test_entry_reason_off(self):
|
||||
out = effective_entry_reason_options(FULL_OPTS, MODE_RISK, False)
|
||||
self.assertNotIn("关键位箱体突破", out)
|
||||
self.assertNotIn("关键位回调触价开仓", out)
|
||||
self.assertIn("顺势加仓", out)
|
||||
|
||||
def test_entry_reason_risk_on(self):
|
||||
out = effective_entry_reason_options(FULL_OPTS, MODE_RISK, True)
|
||||
self.assertIn("关键位箱体突破", out)
|
||||
self.assertIn("关键位回调触价开仓", out)
|
||||
|
||||
def test_entry_reason_full_margin_on(self):
|
||||
out = effective_entry_reason_options(FULL_OPTS, MODE_FULL_MARGIN, True)
|
||||
self.assertNotIn("关键位箱体突破", out)
|
||||
self.assertIn("关键位回调触价开仓", out)
|
||||
|
||||
def test_stats_segments_off(self):
|
||||
segs = effective_stats_segment_defs(STATS_DEFS, MODE_RISK, False)
|
||||
keys = {x[0] for x in segs}
|
||||
self.assertIn("all", keys)
|
||||
self.assertNotIn("key_box", keys)
|
||||
|
||||
def test_add_key_rs_always(self):
|
||||
ok, _ = check_monitor_type_add_allowed("关键支撑阻力", MODE_RISK, False)
|
||||
self.assertTrue(ok)
|
||||
|
||||
def test_add_key_trigger_off(self):
|
||||
ok, msg = check_monitor_type_add_allowed("回调触价开仓", MODE_RISK, False)
|
||||
self.assertFalse(ok)
|
||||
self.assertIn("KEY_AUTO_ORDER_ENABLED", msg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user