From 80cc26ef273ea8559da3a2bb753c5179d3873e89 Mon Sep 17 00:00:00 2001 From: dekun Date: Mon, 25 May 2026 07:21:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dokx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crypto_monitor_okx/.env.example | 329 ++++++++++++------------ crypto_monitor_okx/app.py | 97 ++++++- crypto_monitor_okx/templates/index.html | 45 +++- 3 files changed, 299 insertions(+), 172 deletions(-) diff --git a/crypto_monitor_okx/.env.example b/crypto_monitor_okx/.env.example index 672d6f9..02f13c6 100644 --- a/crypto_monitor_okx/.env.example +++ b/crypto_monitor_okx/.env.example @@ -1,164 +1,165 @@ -# ============================================================================= -# 环境配置模板(可提交 Git)。程序运行时只读取同目录下的 .env。 -# -# 首次部署 / 新机: -# cp .env.example .env -# nano .env # 填入真实密钥、端口、代理等 -# -# 升级代码(git pull)前建议备份(.env 不在 Git 中,pull 不会覆盖): -# cp .env .env.backup.$(date +%Y%m%d) -# -# 从备份恢复: -# cp .env.backup.YYYYMMDD .env -# ============================================================================= - -APP_ENV=production -# 服务监听地址(云服务器通常用 0.0.0.0) -APP_HOST=0.0.0.0 -# 服务端口 -APP_PORT=5004 -# 是否开启调试模式(生产建议 false) -APP_DEBUG=false - -# 登录账号 -APP_USERNAME=dekun -# 登录密码(请改成你自己的强密码) -APP_PASSWORD=ChangeMe123! -# 是否关闭登录校验(局域网可设 true;公网务必 false) -APP_AUTH_DISABLED=true -# --- 多账户交易中控 manual_trading_hub --- -# 中控请求本实例 /api/hub/* 时携带请求头 X-Hub-Token,须与中控启动环境变量 HUB_BRIDGE_TOKEN 一致 -# 未设置且 APP_AUTH_DISABLED=false 时,仅网页登录后可访问;本机联调可保持 APP_AUTH_DISABLED=true -# HUB_BRIDGE_TOKEN=your-long-random-token -# Flask 会话密钥(必须替换为长随机字符串) -FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET - -# 企业微信机器人 Webhook(用于行情/风控推送) -WECHAT_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=REPLACE_WITH_REAL_KEY - -# 数据库文件路径(相对路径会自动按项目目录解析) -DB_PATH=crypto.db -# 交易截图上传目录 -UPLOAD_DIR=static/images - -# 训练总资金(U) -# TOTAL_CAPITAL=100 # 已弃用,资金展示读交易所 -# 每天起始基数(U) -DAILY_START_CAPITAL=30 -# 日内回撤后基数(U) -DAILY_LOSS_CAPITAL=20 -# 日内盈利后基数(U) -DAILY_PROFIT_CAPITAL=50 -# BTC 默认杠杆倍数 -BTC_LEVERAGE=10 -# 山寨币默认杠杆倍数 -ALT_LEVERAGE=5 -# 交易日重置小时(北京时间) -TRADING_DAY_RESET_HOUR=8 - -# 是否开启 OKX 实盘下单(false=只做本地流程,true=真实下单) -LIVE_TRADING_ENABLED=true -# OKX API Key(实盘) -OKX_API_KEY=REPLACE_WITH_OKX_API_KEY -# OKX API Secret(实盘) -OKX_API_SECRET=REPLACE_WITH_OKX_API_SECRET -# OKX API Passphrase(实盘) -OKX_API_PASSPHRASE=REPLACE_WITH_OKX_API_PASSPHRASE -# 保证金模式:cross=全仓,isolated=逐仓 -OKX_TD_MODE=cross -# 持仓模式:hedge=双向持仓,net=单向净持仓 -OKX_POS_MODE=hedge -# 仓位查询 instType(OKX) -OKX_POSITION_INST_TYPE=SWAP - -# 关键位监控:5m收线突破过滤参数 -KLINE_TIMEFRAME=5m -KEY_BREAKOUT_LIMIT_PCT=1.5 -KEY_ALERT_MAX_TIMES=3 -KEY_ALERT_INTERVAL_MINUTES=5 - -# 资金与仓位刷新周期(秒) -BALANCE_REFRESH_SECONDS=60 -# 后台监控轮询周期(秒) -MONITOR_POLL_SECONDS=3 -# 使用可用资金时的缓冲比例(如0.98代表用98%) -FULL_MARGIN_BUFFER_RATIO=0.98 - -# 自动划转:将目标账户补足到 AUTO_TRANSFER_AMOUNT -AUTO_TRANSFER_ENABLED=false -AUTO_TRANSFER_AMOUNT=30 -AUTO_TRANSFER_FROM=funding -AUTO_TRANSFER_TO=swap -TRANSFER_CCY=USDT -# 强制清仓整点(北京时间,默认 0=凌晨00点) -FORCE_CLOSE_BJ_HOUR=0 -# 是否启用强制清仓(默认关闭,true 才会在整点执行) -FORCE_CLOSE_ENABLED=false - -# 推送与AI超时(秒) -WECHAT_TIMEOUT_SECONDS=10 -AI_TIMEOUT_SECONDS=120 - -# AI 复盘服务地址(本机 Ollama 默认地址) -AI_PROVIDER=openai -OPENAI_API_BASE=https://op.bz121.com/v1 -OPENAI_API_KEY=你的密钥 -OPENAI_MODEL=gemma4:e4b -OLLAMA_API=http://127.0.0.1:11434/api/generate -AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest - -# OKX 代理(可选):用于本机网络对 OKX TLS/SNI 不稳定时,通过 SSH 动态转发 SOCKS5 出口 -# 1) 先在本机建立隧道(示例): -# ssh -N -D 127.0.0.1:1080 root@你的VPS_IP -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -# 2) 再启用下面这一行(推荐 socks5h,让远端解析域名): -# OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080 -# -# 如你更偏向 HTTP 代理(VPS 上跑 tinyproxy 之类),可用: -# OKX_HTTP_PROXY=http://127.0.0.1:3128 -# OKX_HTTPS_PROXY=http://127.0.0.1:3128 - -# 开仓多周期K线图(可选) -# ORDER_CHART_ENABLED=true -# ORDER_CHART_TFS=4h,1h,15m,5m -# ORDER_CHART_LIMIT=100 -# ORDER_CHART_DIR=static/images/order_charts -# DAILY_OPEN_ALERT_THRESHOLD=5 -# 关键位:标准方案止损外侧%、趋势单方案止损外侧%(默认 0.5 / 1) -# KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5 -# KEY_TREND_STOP_OUTSIDE_PCT=1 -# 以损定仓(按交易账户资金的百分比) -# RISK_PERCENT=2 -# 移动保本触发(达到多少R触发)与偏移(百分比) -# BREAKEVEN_RR_TRIGGER=1.0 -# 移动保本阶梯(每多少R继续上移一次,默认1R) -# BREAKEVEN_STEP_R=1.0 -# BREAKEVEN_OFFSET_PCT=0.02 -# 开单风格默认值:trend / swing -# DEFAULT_TRADE_STYLE=trend - -APP_TIMEZONE=Asia/Shanghai -AUTO_TRANSFER_BJ_HOUR=8 -# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日/可开仓等 - -TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true - -MAX_ACTIVE_POSITIONS=1 -MANUAL_MIN_PLANNED_RR=1.4 - -KEY_CONFIRM_BREAKOUT_BAR=-2 -KEY_CONFIRM_BAR=-1 -KEY_VOLUME_MA_BARS=20 -KEY_VOLUME_RATIO_MIN=1.3 -# 【箱体/收敛】突破K收盘越过关键位下限% -KEY_BREAKOUT_AMP_MIN_PCT=0.03 -KEY_BREAKOUT_AMP_MAX_PCT=0.5 -# 【阻力/支撑】突破后微信提醒 -KEY_ALERT_MAX_TIMES=3 -KEY_ALERT_INTERVAL_MINUTES=5 - -EXCHANGE_DISPLAY_NAME=OKX -OKX_ACCOUNT_LABEL= - -BACKUP_ROOT=/root/backups -BACKUP_RETENTION_DAYS=30 -BACKUP_INSTANCE=crypto_monitor_okx +# ============================================================================= +# 环境配置模板(可提交 Git)。程序运行时只读取同目录下的 .env。 +# +# 首次部署 / 新机: +# cp .env.example .env +# nano .env # 填入真实密钥、端口、代理等 +# +# 升级代码(git pull)前建议备份(.env 不在 Git 中,pull 不会覆盖): +# cp .env .env.backup.$(date +%Y%m%d) +# +# 从备份恢复: +# cp .env.backup.YYYYMMDD .env +# ============================================================================= + +APP_ENV=production +# 服务监听地址(云服务器通常用 0.0.0.0) +APP_HOST=0.0.0.0 +# 服务端口 +APP_PORT=5004 +# 是否开启调试模式(生产建议 false) +APP_DEBUG=false + +# 登录账号 +APP_USERNAME=dekun +# 登录密码(请改成你自己的强密码) +APP_PASSWORD=ChangeMe123! +# 是否关闭登录校验(局域网可设 true;公网务必 false) +APP_AUTH_DISABLED=true +# --- 多账户交易中控 manual_trading_hub --- +# 中控请求本实例 /api/hub/* 时携带请求头 X-Hub-Token,须与中控启动环境变量 HUB_BRIDGE_TOKEN 一致 +# 未设置且 APP_AUTH_DISABLED=false 时,仅网页登录后可访问;本机联调可保持 APP_AUTH_DISABLED=true +# HUB_BRIDGE_TOKEN=your-long-random-token +# Flask 会话密钥(必须替换为长随机字符串) +FLASK_SECRET_KEY=CHANGE_TO_LONG_RANDOM_SECRET + +# 企业微信机器人 Webhook(用于行情/风控推送) +WECHAT_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=REPLACE_WITH_REAL_KEY + +# 数据库文件路径(相对路径会自动按项目目录解析) +DB_PATH=crypto.db +# 交易截图上传目录 +UPLOAD_DIR=static/images + +# 训练总资金(U) +# TOTAL_CAPITAL=100 # 已弃用,资金展示读交易所 +# 每天起始基数(U) +DAILY_START_CAPITAL=30 +# 日内回撤后基数(U) +DAILY_LOSS_CAPITAL=20 +# 日内盈利后基数(U) +DAILY_PROFIT_CAPITAL=50 +# BTC 默认杠杆倍数 +BTC_LEVERAGE=10 +# 山寨币默认杠杆倍数 +ALT_LEVERAGE=5 +# 交易日重置小时(北京时间) +TRADING_DAY_RESET_HOUR=8 + +# 是否开启 OKX 实盘下单(false=只做本地流程,true=真实下单) +LIVE_TRADING_ENABLED=true +# OKX API Key(实盘) +OKX_API_KEY=REPLACE_WITH_OKX_API_KEY +# OKX API Secret(实盘) +OKX_API_SECRET=REPLACE_WITH_OKX_API_SECRET +# OKX API Passphrase(实盘) +OKX_API_PASSPHRASE=REPLACE_WITH_OKX_API_PASSPHRASE +# 保证金模式:cross=全仓,isolated=逐仓 +OKX_TD_MODE=cross +# 持仓模式:hedge=双向持仓,net=单向净持仓 +OKX_POS_MODE=hedge +# 仓位查询 instType(OKX) +OKX_POSITION_INST_TYPE=SWAP + +# 关键位监控:5m收线突破过滤参数 +KLINE_TIMEFRAME=5m +KEY_BREAKOUT_LIMIT_PCT=1.5 +KEY_ALERT_MAX_TIMES=3 +KEY_ALERT_INTERVAL_MINUTES=5 + +# 资金与仓位刷新周期(秒) +BALANCE_REFRESH_SECONDS=60 +# 后台监控轮询周期(秒) +MONITOR_POLL_SECONDS=3 +# 使用可用资金时的缓冲比例(如0.98代表用98%) +FULL_MARGIN_BUFFER_RATIO=0.98 + +# 自动划转:将目标账户补足到 AUTO_TRANSFER_AMOUNT +AUTO_TRANSFER_ENABLED=false +AUTO_TRANSFER_AMOUNT=30 +AUTO_TRANSFER_FROM=funding +AUTO_TRANSFER_TO=swap +TRANSFER_CCY=USDT +# 强制清仓整点(北京时间,默认 0=凌晨00点) +FORCE_CLOSE_BJ_HOUR=0 +# 是否启用强制清仓(默认关闭,true 才会在整点执行) +FORCE_CLOSE_ENABLED=false + +# 推送与AI超时(秒) +WECHAT_TIMEOUT_SECONDS=10 +AI_TIMEOUT_SECONDS=120 + +# AI 复盘服务地址(本机 Ollama 默认地址) +AI_PROVIDER=openai +OPENAI_API_BASE=https://op.bz121.com/v1 +OPENAI_API_KEY=你的密钥 +OPENAI_MODEL=gemma4:e4b +OLLAMA_API=http://127.0.0.1:11434/api/generate +AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest + +# OKX 代理(可选):用于本机网络对 OKX TLS/SNI 不稳定时,通过 SSH 动态转发 SOCKS5 出口 +# 1) 先在本机建立隧道(示例): +# ssh -N -D 127.0.0.1:1080 root@你的VPS_IP -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes +# 2) 再启用下面这一行(推荐 socks5h,让远端解析域名): +# OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080 +# +# 如你更偏向 HTTP 代理(VPS 上跑 tinyproxy 之类),可用: +# OKX_HTTP_PROXY=http://127.0.0.1:3128 +# OKX_HTTPS_PROXY=http://127.0.0.1:3128 + +# 开仓多周期K线图(可选) +# ORDER_CHART_ENABLED=true +# ORDER_CHART_TFS=4h,1h,15m,5m +# ORDER_CHART_LIMIT=100 +# ORDER_CHART_DIR=static/images/order_charts +# DAILY_OPEN_ALERT_THRESHOLD=5 +# 关键位:标准方案止损外侧%、趋势单方案止损外侧%(默认 0.5 / 1) +# KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5 +# KEY_TREND_STOP_OUTSIDE_PCT=1 +# 以损定仓(按交易账户资金的百分比) +# RISK_PERCENT=2 +# 移动保本触发(达到多少R触发)与偏移(百分比) +# BREAKEVEN_RR_TRIGGER=1.0 +# 移动保本阶梯(每多少R继续上移一次,默认1R) +# BREAKEVEN_STEP_R=1.0 +# BREAKEVEN_OFFSET_PCT=0.02 +# 开单风格默认值:trend / swing +# DEFAULT_TRADE_STYLE=trend + +APP_TIMEZONE=Asia/Shanghai +AUTO_TRANSFER_BJ_HOUR=8 +# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日/可开仓等 + +# 默认启用「北京时间 TRADING_DAY_RESET_HOUR 前禁止新开仓」;网页顶栏可运行时切换(写入 DB) +TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true + +MAX_ACTIVE_POSITIONS=1 +MANUAL_MIN_PLANNED_RR=1.4 + +KEY_CONFIRM_BREAKOUT_BAR=-2 +KEY_CONFIRM_BAR=-1 +KEY_VOLUME_MA_BARS=20 +KEY_VOLUME_RATIO_MIN=1.3 +# 【箱体/收敛】突破K收盘越过关键位下限% +KEY_BREAKOUT_AMP_MIN_PCT=0.03 +KEY_BREAKOUT_AMP_MAX_PCT=0.5 +# 【阻力/支撑】突破后微信提醒 +KEY_ALERT_MAX_TIMES=3 +KEY_ALERT_INTERVAL_MINUTES=5 + +EXCHANGE_DISPLAY_NAME=OKX +OKX_ACCOUNT_LABEL= + +BACKUP_ROOT=/root/backups +BACKUP_RETENTION_DAYS=30 +BACKUP_INSTANCE=crypto_monitor_okx diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 5aa2561..352fc6b 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -143,6 +143,7 @@ TRADING_DAY_RESET_HOUR = int(os.getenv("TRADING_DAY_RESET_HOUR", "8")) TRADING_DAY_RESET_OPEN_GUARD_ENABLED = os.getenv( "TRADING_DAY_RESET_OPEN_GUARD_ENABLED", "true" ).lower() in ("1", "true", "yes", "on") +RUNTIME_KEY_OPEN_GUARD = "trading_day_reset_open_guard_enabled" APP_TIMEZONE = os.getenv("APP_TIMEZONE", "Asia/Shanghai") @@ -1064,6 +1065,11 @@ def init_db(): (id INTEGER PRIMARY KEY AUTOINCREMENT, transfer_type TEXT, transfer_day TEXT, amount REAL, from_account TEXT, to_account TEXT, status TEXT, message TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''') + c.execute( + """CREATE TABLE IF NOT EXISTS app_runtime_settings + (key TEXT PRIMARY KEY, value TEXT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""" + ) c.execute('''DROP INDEX IF EXISTS idx_transfer_logs_unique_day''') c.execute('''CREATE UNIQUE INDEX IF NOT EXISTS idx_transfer_logs_auto_daily_unique ON transfer_logs(transfer_type, transfer_day) @@ -2233,8 +2239,45 @@ def auto_transfer_once_per_day(): ) -def trading_day_reset_allows_new_open(now): - if not TRADING_DAY_RESET_OPEN_GUARD_ENABLED: +def get_trading_day_reset_open_guard_enabled(conn=None): + """True=启用整点限制(默认 8:00 前禁止新开仓/登记监控)。""" + owns = conn is None + if owns: + conn = get_db() + try: + row = conn.execute( + "SELECT value FROM app_runtime_settings WHERE key=?", + (RUNTIME_KEY_OPEN_GUARD,), + ).fetchone() + if row is not None: + return str(row[0]).lower() in ("1", "true", "yes", "on") + except Exception: + pass + finally: + if owns: + conn.close() + return TRADING_DAY_RESET_OPEN_GUARD_ENABLED + + +def set_trading_day_reset_open_guard_enabled(enabled: bool, conn=None): + owns = conn is None + if owns: + conn = get_db() + try: + conn.execute( + "INSERT INTO app_runtime_settings(key, value, updated_at) VALUES (?,?,?) " + "ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at", + (RUNTIME_KEY_OPEN_GUARD, "1" if enabled else "0", app_now_str()), + ) + if owns: + conn.commit() + finally: + if owns: + conn.close() + + +def trading_day_reset_allows_new_open(now, conn=None): + if not get_trading_day_reset_open_guard_enabled(conn): return True return now.hour >= TRADING_DAY_RESET_HOUR @@ -3821,33 +3864,39 @@ def _finalize_fib_key_fill(conn, row): live_amt = get_live_position_contracts(ex_sym, direction) amount = float(live_amt or 0) if amount <= 0: - send_wechat_msg( + msg = ( f"# ❌ {symbol} 斐波成交后处理失败\n" f"**账户:{_wechat_account_label()}**\n" f"- 无法取得持仓/下单数量,未挂 TP/SL\n" ) + send_wechat_msg(msg) + _finalize_key_monitor_one_shot(conn, row, msg, "fib_fill_no_amount") return ok, reason = precheck_risk(conn, symbol, direction) if not ok: - send_wechat_msg( + msg = ( f"# ❌ {symbol} 斐波成交后风控拒绝\n" f"**账户:{_wechat_account_label()}**\n" f"- 类型:{typ}\n" f"- 原因:{reason}\n" f"- 请手动处理仓位与挂单\n" ) + send_wechat_msg(msg) + _finalize_key_monitor_one_shot(conn, row, msg, "fib_risk_rejected") return tpsl_attached = False try: _okx_place_tp_sl_orders(ex_sym, direction, amount, sl, tp) tpsl_attached = True except Exception as e: - send_wechat_msg( + msg = ( f"# ❌ {symbol} 斐波成交后挂 TP/SL 失败\n" f"**账户:{_wechat_account_label()}**\n" f"- 错误:{friendly_okx_error(e)}\n" f"- 请手动补挂止盈止损\n" ) + send_wechat_msg(msg) + _finalize_key_monitor_one_shot(conn, row, msg, "fib_tpsl_failed") return contract_size = get_contract_size(ex_sym) base_amount = round(float(amount) * contract_size, 8) @@ -4624,7 +4673,9 @@ def render_main_page(page="trade"): ) rate = round(win/total*100,2) if total else 0 active_count = len(order_list) - can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS + open_guard_enabled = get_trading_day_reset_open_guard_enabled(conn) + open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR + can_trade = trading_day_reset_allows_new_open(now, conn) and active_count < MAX_ACTIVE_POSITIONS key_gate_rule_text = ( f"【箱体/收敛】{KLINE_TIMEFRAME} 两根闭合K|突破越过关键位 > {KEY_BREAKOUT_AMP_MIN_PCT}%|" f"确认K收于箱外|量能>前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}|" @@ -4661,6 +4712,8 @@ def render_main_page(page="trade"): btc_leverage=BTC_LEVERAGE, alt_leverage=ALT_LEVERAGE, reset_hour=TRADING_DAY_RESET_HOUR, + open_guard_enabled=open_guard_enabled, + open_guard_blocks_now=open_guard_blocks_now, balance_refresh_seconds=BALANCE_REFRESH_SECONDS, auto_transfer_enabled=AUTO_TRANSFER_ENABLED, auto_transfer_amount=AUTO_TRANSFER_AMOUNT, @@ -4744,7 +4797,9 @@ def api_account_snapshot(): current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS) recommended_capital = get_recommended_capital(current_capital) active_count = get_active_position_count(conn) + open_guard_enabled = get_trading_day_reset_open_guard_enabled(conn) conn.close() + open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS available_trading_usdt = get_available_trading_usdt() return jsonify({ @@ -4755,11 +4810,41 @@ def api_account_snapshot(): "active_count": active_count, "max_active_positions": MAX_ACTIVE_POSITIONS, "can_trade": can_trade, + "open_guard_enabled": open_guard_enabled, + "open_guard_blocks_now": open_guard_blocks_now, + "reset_hour": TRADING_DAY_RESET_HOUR, "manual_min_planned_rr": MANUAL_MIN_PLANNED_RR, "trading_day": trading_day, }) +@app.route("/api/settings/open_guard", methods=["POST"]) +@login_required +def api_settings_open_guard(): + data = request.get_json(silent=True) or {} + raw = data.get("enabled") + if raw is None: + raw = request.form.get("enabled") + if raw is None: + return jsonify({"ok": False, "msg": "缺少 enabled 参数"}), 400 + enabled = str(raw).lower() in ("1", "true", "yes", "on") + set_trading_day_reset_open_guard_enabled(enabled) + now = app_now() + conn = get_db() + active_count = get_active_position_count(conn) + guard_on = get_trading_day_reset_open_guard_enabled(conn) + conn.close() + can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS + return jsonify( + { + "ok": True, + "open_guard_enabled": guard_on, + "can_trade": can_trade, + "reset_hour": TRADING_DAY_RESET_HOUR, + } + ) + + @app.route("/api/price_snapshot") @login_required def api_price_snapshot(): diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index fbcb715..6a33c21 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -258,6 +258,15 @@
当日资金(交易账户)
{{ funds_fmt(current_capital) }}U
实时价格更新时间:--(北京时间 UTC+8)
+
+ + + {% if open_guard_enabled %}已限制:{{ reset_hour }}:00 前不可开仓{% else %}已放开:{{ reset_hour }}:00 前允许开仓{% endif %} + +
{% if page == 'key_monitor' %} @@ -379,7 +388,7 @@
规则:最多 {{ max_active_positions }} 仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x; - {% if can_trade %}可开仓{% else %}不可开仓(持仓已满或未到北京时间 {{ reset_hour }}:00){% endif %}; + {% if can_trade %}可开仓{% else %}不可开仓{% if active_count >= max_active_positions %}(持仓 {{ active_count }}/{{ max_active_positions }}){% endif %}{% if open_guard_blocks_now %}(未到北京时间 {{ reset_hour }}:00){% endif %}{% endif %}; 人工开仓盈亏比不得低于 {{ manual_min_planned_rr }}:1
@@ -1745,15 +1754,47 @@ function refreshAccountSnapshot(){ if (typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null) { latestAvailableUsdt = Number(data.available_trading_usdt); } - const canTradeText = data.can_trade ? "可开仓" : `不可开仓(持仓 ${data.active_count||0}/${data.max_active_positions||{{ max_active_positions }}} 或未到北京时间 {{ reset_hour }}:00)`; + let canTradeText = "可开仓"; + if(!data.can_trade){ + const parts = []; + if((data.active_count||0) >= (data.max_active_positions||{{ max_active_positions }})) parts.push(`持仓 ${data.active_count}/${data.max_active_positions}`); + if(data.open_guard_blocks_now) parts.push(`未到北京时间 ${data.reset_hour||{{ reset_hour }}}:00`); + canTradeText = parts.length ? `不可开仓(${parts.join(";")})` : "不可开仓"; + } const tip = document.getElementById("order-rule-tip"); const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt.toFixed(2)}U` : ""; if(tip){ tip.innerText = `规则:最多 ${data.max_active_positions || {{ max_active_positions }}} 仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x;${canTradeText}${avail}`; } + const allowEl = document.getElementById("allow-open-before-reset"); + const guardStatus = document.getElementById("open-guard-status"); + const resetH = data.reset_hour != null ? data.reset_hour : {{ reset_hour }}; + if(allowEl && typeof data.open_guard_enabled !== "undefined"){ + allowEl.checked = !data.open_guard_enabled; + } + if(guardStatus && typeof data.open_guard_enabled !== "undefined"){ + guardStatus.innerText = data.open_guard_enabled + ? `已限制:${resetH}:00 前不可开仓` + : `已放开:${resetH}:00 前允许开仓`; + } }).catch(()=>{}); } +const allowOpenBeforeResetEl = document.getElementById("allow-open-before-reset"); +if(allowOpenBeforeResetEl){ + allowOpenBeforeResetEl.addEventListener("change", function(){ + const allow = !!this.checked; + fetch("/api/settings/open_guard", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({enabled: !allow}), + }).then(r=>r.json()).then(data=>{ + if(!data.ok){ alert(data.msg || "保存失败"); return; } + refreshAccountSnapshot(); + }).catch(()=>alert("保存失败")); + }); +} + const orderSymbolEl = document.getElementById("order-symbol"); const orderDirectionEl = document.getElementById("order-direction"); const fullMarginEl = document.getElementById("use-full-margin");