修复okx
This commit is contained in:
@@ -140,6 +140,7 @@ APP_TIMEZONE=Asia/Shanghai
|
|||||||
AUTO_TRANSFER_BJ_HOUR=8
|
AUTO_TRANSFER_BJ_HOUR=8
|
||||||
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日/可开仓等
|
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日/可开仓等
|
||||||
|
|
||||||
|
# 默认启用「北京时间 TRADING_DAY_RESET_HOUR 前禁止新开仓」;网页顶栏可运行时切换(写入 DB)
|
||||||
TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
||||||
|
|
||||||
MAX_ACTIVE_POSITIONS=1
|
MAX_ACTIVE_POSITIONS=1
|
||||||
|
|||||||
@@ -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 = os.getenv(
|
||||||
"TRADING_DAY_RESET_OPEN_GUARD_ENABLED", "true"
|
"TRADING_DAY_RESET_OPEN_GUARD_ENABLED", "true"
|
||||||
).lower() in ("1", "true", "yes", "on")
|
).lower() in ("1", "true", "yes", "on")
|
||||||
|
RUNTIME_KEY_OPEN_GUARD = "trading_day_reset_open_guard_enabled"
|
||||||
APP_TIMEZONE = os.getenv("APP_TIMEZONE", "Asia/Shanghai")
|
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,
|
(id INTEGER PRIMARY KEY AUTOINCREMENT, transfer_type TEXT, transfer_day TEXT,
|
||||||
amount REAL, from_account TEXT, to_account TEXT, status TEXT, message TEXT,
|
amount REAL, from_account TEXT, to_account TEXT, status TEXT, message TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
|
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('''DROP INDEX IF EXISTS idx_transfer_logs_unique_day''')
|
||||||
c.execute('''CREATE UNIQUE INDEX IF NOT EXISTS idx_transfer_logs_auto_daily_unique
|
c.execute('''CREATE UNIQUE INDEX IF NOT EXISTS idx_transfer_logs_auto_daily_unique
|
||||||
ON transfer_logs(transfer_type, transfer_day)
|
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):
|
def get_trading_day_reset_open_guard_enabled(conn=None):
|
||||||
if not TRADING_DAY_RESET_OPEN_GUARD_ENABLED:
|
"""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 True
|
||||||
return now.hour >= TRADING_DAY_RESET_HOUR
|
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)
|
live_amt = get_live_position_contracts(ex_sym, direction)
|
||||||
amount = float(live_amt or 0)
|
amount = float(live_amt or 0)
|
||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
send_wechat_msg(
|
msg = (
|
||||||
f"# ❌ {symbol} 斐波成交后处理失败\n"
|
f"# ❌ {symbol} 斐波成交后处理失败\n"
|
||||||
f"**账户:{_wechat_account_label()}**\n"
|
f"**账户:{_wechat_account_label()}**\n"
|
||||||
f"- 无法取得持仓/下单数量,未挂 TP/SL\n"
|
f"- 无法取得持仓/下单数量,未挂 TP/SL\n"
|
||||||
)
|
)
|
||||||
|
send_wechat_msg(msg)
|
||||||
|
_finalize_key_monitor_one_shot(conn, row, msg, "fib_fill_no_amount")
|
||||||
return
|
return
|
||||||
ok, reason = precheck_risk(conn, symbol, direction)
|
ok, reason = precheck_risk(conn, symbol, direction)
|
||||||
if not ok:
|
if not ok:
|
||||||
send_wechat_msg(
|
msg = (
|
||||||
f"# ❌ {symbol} 斐波成交后风控拒绝\n"
|
f"# ❌ {symbol} 斐波成交后风控拒绝\n"
|
||||||
f"**账户:{_wechat_account_label()}**\n"
|
f"**账户:{_wechat_account_label()}**\n"
|
||||||
f"- 类型:{typ}\n"
|
f"- 类型:{typ}\n"
|
||||||
f"- 原因:{reason}\n"
|
f"- 原因:{reason}\n"
|
||||||
f"- 请手动处理仓位与挂单\n"
|
f"- 请手动处理仓位与挂单\n"
|
||||||
)
|
)
|
||||||
|
send_wechat_msg(msg)
|
||||||
|
_finalize_key_monitor_one_shot(conn, row, msg, "fib_risk_rejected")
|
||||||
return
|
return
|
||||||
tpsl_attached = False
|
tpsl_attached = False
|
||||||
try:
|
try:
|
||||||
_okx_place_tp_sl_orders(ex_sym, direction, amount, sl, tp)
|
_okx_place_tp_sl_orders(ex_sym, direction, amount, sl, tp)
|
||||||
tpsl_attached = True
|
tpsl_attached = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
send_wechat_msg(
|
msg = (
|
||||||
f"# ❌ {symbol} 斐波成交后挂 TP/SL 失败\n"
|
f"# ❌ {symbol} 斐波成交后挂 TP/SL 失败\n"
|
||||||
f"**账户:{_wechat_account_label()}**\n"
|
f"**账户:{_wechat_account_label()}**\n"
|
||||||
f"- 错误:{friendly_okx_error(e)}\n"
|
f"- 错误:{friendly_okx_error(e)}\n"
|
||||||
f"- 请手动补挂止盈止损\n"
|
f"- 请手动补挂止盈止损\n"
|
||||||
)
|
)
|
||||||
|
send_wechat_msg(msg)
|
||||||
|
_finalize_key_monitor_one_shot(conn, row, msg, "fib_tpsl_failed")
|
||||||
return
|
return
|
||||||
contract_size = get_contract_size(ex_sym)
|
contract_size = get_contract_size(ex_sym)
|
||||||
base_amount = round(float(amount) * contract_size, 8)
|
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
|
rate = round(win/total*100,2) if total else 0
|
||||||
active_count = len(order_list)
|
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 = (
|
key_gate_rule_text = (
|
||||||
f"【箱体/收敛】{KLINE_TIMEFRAME} 两根闭合K|突破越过关键位 > {KEY_BREAKOUT_AMP_MIN_PCT}%|"
|
f"【箱体/收敛】{KLINE_TIMEFRAME} 两根闭合K|突破越过关键位 > {KEY_BREAKOUT_AMP_MIN_PCT}%|"
|
||||||
f"确认K收于箱外|量能>前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}|"
|
f"确认K收于箱外|量能>前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}|"
|
||||||
@@ -4661,6 +4712,8 @@ def render_main_page(page="trade"):
|
|||||||
btc_leverage=BTC_LEVERAGE,
|
btc_leverage=BTC_LEVERAGE,
|
||||||
alt_leverage=ALT_LEVERAGE,
|
alt_leverage=ALT_LEVERAGE,
|
||||||
reset_hour=TRADING_DAY_RESET_HOUR,
|
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,
|
balance_refresh_seconds=BALANCE_REFRESH_SECONDS,
|
||||||
auto_transfer_enabled=AUTO_TRANSFER_ENABLED,
|
auto_transfer_enabled=AUTO_TRANSFER_ENABLED,
|
||||||
auto_transfer_amount=AUTO_TRANSFER_AMOUNT,
|
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)
|
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)
|
recommended_capital = get_recommended_capital(current_capital)
|
||||||
active_count = get_active_position_count(conn)
|
active_count = get_active_position_count(conn)
|
||||||
|
open_guard_enabled = get_trading_day_reset_open_guard_enabled(conn)
|
||||||
conn.close()
|
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
|
can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS
|
||||||
available_trading_usdt = get_available_trading_usdt()
|
available_trading_usdt = get_available_trading_usdt()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
@@ -4755,11 +4810,41 @@ def api_account_snapshot():
|
|||||||
"active_count": active_count,
|
"active_count": active_count,
|
||||||
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||||
"can_trade": can_trade,
|
"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,
|
"manual_min_planned_rr": MANUAL_MIN_PLANNED_RR,
|
||||||
"trading_day": trading_day,
|
"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")
|
@app.route("/api/price_snapshot")
|
||||||
@login_required
|
@login_required
|
||||||
def api_price_snapshot():
|
def api_price_snapshot():
|
||||||
|
|||||||
@@ -258,6 +258,15 @@
|
|||||||
<div class="stat-item"><div class="label">当日资金(交易账户)</div><div class="value" id="current-capital">{{ funds_fmt(current_capital) }}U</div></div>
|
<div class="stat-item"><div class="label">当日资金(交易账户)</div><div class="value" id="current-capital">{{ funds_fmt(current_capital) }}U</div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rule-tip">实时价格更新时间:<span id="price-last-updated">--</span>(北京时间 UTC+8)</div>
|
<div class="rule-tip">实时价格更新时间:<span id="price-last-updated">--</span>(北京时间 UTC+8)</div>
|
||||||
|
<div class="rule-tip" id="open-guard-bar" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
||||||
|
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;color:#cfd3ef">
|
||||||
|
<input type="checkbox" id="allow-open-before-reset" {% if not open_guard_enabled %}checked{% endif %}>
|
||||||
|
允许北京时间 {{ reset_hour }}:00 前开仓(斐波成交登记、人工下单)
|
||||||
|
</label>
|
||||||
|
<span id="open-guard-status" style="color:#8892b0;font-size:.75rem">
|
||||||
|
{% if open_guard_enabled %}已限制:{{ reset_hour }}:00 前不可开仓{% else %}已放开:{{ reset_hour }}:00 前允许开仓{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{% if page == 'key_monitor' %}
|
{% if page == 'key_monitor' %}
|
||||||
@@ -379,7 +388,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="rule-tip" id="order-rule-tip">
|
<div class="rule-tip" id="order-rule-tip">
|
||||||
规则:最多 {{ max_active_positions }} 仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x;
|
规则:最多 {{ 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
|
人工开仓盈亏比不得低于 {{ manual_min_planned_rr }}:1
|
||||||
</div>
|
</div>
|
||||||
<div class="rule-tip">
|
<div class="rule-tip">
|
||||||
@@ -1745,15 +1754,47 @@ function refreshAccountSnapshot(){
|
|||||||
if (typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null) {
|
if (typeof data.available_trading_usdt !== "undefined" && data.available_trading_usdt !== null) {
|
||||||
latestAvailableUsdt = Number(data.available_trading_usdt);
|
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 tip = document.getElementById("order-rule-tip");
|
||||||
const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt.toFixed(2)}U` : "";
|
const avail = (latestAvailableUsdt !== null && !Number.isNaN(latestAvailableUsdt)) ? `;交易账户可用约${latestAvailableUsdt.toFixed(2)}U` : "";
|
||||||
if(tip){
|
if(tip){
|
||||||
tip.innerText = `规则:最多 ${data.max_active_positions || {{ max_active_positions }}} 仓;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x;${canTradeText}${avail}`;
|
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(()=>{});
|
}).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 orderSymbolEl = document.getElementById("order-symbol");
|
||||||
const orderDirectionEl = document.getElementById("order-direction");
|
const orderDirectionEl = document.getElementById("order-direction");
|
||||||
const fullMarginEl = document.getElementById("use-full-margin");
|
const fullMarginEl = document.getElementById("use-full-margin");
|
||||||
|
|||||||
Reference in New Issue
Block a user