feat: instance system settings page with risk docs, export and transfer
顶栏新增系统设置;风控说明读取 .env;数据导出与资金划转迁入三张卡片;顶栏移除导出条与手动划转。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7239,6 +7239,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
orphan_live_positions = list_orphan_live_positions(conn)
|
orphan_live_positions = list_orphan_live_positions(conn)
|
||||||
conn.close()
|
conn.close()
|
||||||
from lib.instance.instance_embed_lib import embed_context_extras
|
from lib.instance.instance_embed_lib import embed_context_extras
|
||||||
|
from lib.instance.instance_settings_lib import settings_page_context
|
||||||
|
|
||||||
template_ctx = dict(
|
template_ctx = dict(
|
||||||
page=page,
|
page=page,
|
||||||
@@ -7315,6 +7316,14 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
kline_timeframe=KLINE_TIMEFRAME,
|
kline_timeframe=KLINE_TIMEFRAME,
|
||||||
**strategy_extra,
|
**strategy_extra,
|
||||||
**embed_context_extras("binance"),
|
**embed_context_extras("binance"),
|
||||||
|
**settings_page_context(
|
||||||
|
page,
|
||||||
|
exchange_key="binance",
|
||||||
|
exchange_display=EXCHANGE_DISPLAY_NAME,
|
||||||
|
risk_status=risk_status,
|
||||||
|
trade_policy=TRADE_POLICY,
|
||||||
|
data_export_version=3,
|
||||||
|
),
|
||||||
**force_close_template_context(
|
**force_close_template_context(
|
||||||
FORCE_CLOSE_ENABLED,
|
FORCE_CLOSE_ENABLED,
|
||||||
FORCE_CLOSE_BJ_HOUR,
|
FORCE_CLOSE_BJ_HOUR,
|
||||||
@@ -7358,6 +7367,12 @@ def stats_page():
|
|||||||
return render_main_page("stats")
|
return render_main_page("stats")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/settings")
|
||||||
|
@login_required
|
||||||
|
def settings_page():
|
||||||
|
return render_main_page("settings")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/account_snapshot")
|
@app.route("/api/account_snapshot")
|
||||||
@login_required
|
@login_required
|
||||||
def api_account_snapshot():
|
def api_account_snapshot():
|
||||||
@@ -9615,7 +9630,7 @@ def manual_transfer():
|
|||||||
amount = float(request.form.get("amount", "0"))
|
amount = float(request.form.get("amount", "0"))
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("划转金额格式错误")
|
flash("划转金额格式错误")
|
||||||
return redirect("/")
|
return redirect("/settings")
|
||||||
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
||||||
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
||||||
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
||||||
@@ -9630,7 +9645,7 @@ def manual_transfer():
|
|||||||
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
||||||
else:
|
else:
|
||||||
flash(f"手动划转失败:{msg}")
|
flash(f"手动划转失败:{msg}")
|
||||||
return redirect("/")
|
return redirect("/settings")
|
||||||
|
|
||||||
|
|
||||||
def _journal_ai_chart_builder(row):
|
def _journal_ai_chart_builder(row):
|
||||||
|
|||||||
@@ -7033,6 +7033,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
)
|
)
|
||||||
conn.close()
|
conn.close()
|
||||||
from lib.instance.instance_embed_lib import embed_context_extras
|
from lib.instance.instance_embed_lib import embed_context_extras
|
||||||
|
from lib.instance.instance_settings_lib import settings_page_context
|
||||||
|
|
||||||
template_ctx = dict(
|
template_ctx = dict(
|
||||||
page=page,
|
page=page,
|
||||||
@@ -7118,6 +7119,14 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
has_active_positions=bool(order_list),
|
has_active_positions=bool(order_list),
|
||||||
),
|
),
|
||||||
**embed_context_extras("gate"),
|
**embed_context_extras("gate"),
|
||||||
|
**settings_page_context(
|
||||||
|
page,
|
||||||
|
exchange_key="gate",
|
||||||
|
exchange_display=EXCHANGE_DISPLAY_NAME,
|
||||||
|
risk_status=risk_status,
|
||||||
|
trade_policy=TRADE_POLICY,
|
||||||
|
data_export_version=3,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if embed_mode == "fragment":
|
if embed_mode == "fragment":
|
||||||
return render_template("embed_page_fragment.html", **template_ctx)
|
return render_template("embed_page_fragment.html", **template_ctx)
|
||||||
@@ -7173,6 +7182,12 @@ def stats_page():
|
|||||||
return render_main_page("stats")
|
return render_main_page("stats")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/settings")
|
||||||
|
@login_required
|
||||||
|
def settings_page():
|
||||||
|
return render_main_page("settings")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/account_snapshot")
|
@app.route("/api/account_snapshot")
|
||||||
@login_required
|
@login_required
|
||||||
def api_account_snapshot():
|
def api_account_snapshot():
|
||||||
@@ -9487,7 +9502,7 @@ def manual_transfer():
|
|||||||
amount = float(request.form.get("amount", "0"))
|
amount = float(request.form.get("amount", "0"))
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("划转金额格式错误")
|
flash("划转金额格式错误")
|
||||||
return redirect("/")
|
return redirect("/settings")
|
||||||
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
||||||
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
||||||
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
||||||
@@ -9502,7 +9517,7 @@ def manual_transfer():
|
|||||||
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
||||||
else:
|
else:
|
||||||
flash(f"手动划转失败:{msg}")
|
flash(f"手动划转失败:{msg}")
|
||||||
return redirect(request.referrer or "/trade")
|
return redirect("/settings")
|
||||||
|
|
||||||
|
|
||||||
def _journal_ai_chart_builder(row):
|
def _journal_ai_chart_builder(row):
|
||||||
|
|||||||
@@ -6583,6 +6583,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
)
|
)
|
||||||
conn.close()
|
conn.close()
|
||||||
from lib.instance.instance_embed_lib import embed_context_extras
|
from lib.instance.instance_embed_lib import embed_context_extras
|
||||||
|
from lib.instance.instance_settings_lib import settings_page_context
|
||||||
|
|
||||||
template_ctx = dict(
|
template_ctx = dict(
|
||||||
page=page,
|
page=page,
|
||||||
@@ -6661,6 +6662,14 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
exchange_pnl_sync=exchange_pnl_sync,
|
exchange_pnl_sync=exchange_pnl_sync,
|
||||||
**strategy_extra,
|
**strategy_extra,
|
||||||
**embed_context_extras("okx"),
|
**embed_context_extras("okx"),
|
||||||
|
**settings_page_context(
|
||||||
|
page,
|
||||||
|
exchange_key="okx",
|
||||||
|
exchange_display=EXCHANGE_DISPLAY_NAME,
|
||||||
|
risk_status=risk_status,
|
||||||
|
trade_policy=TRADE_POLICY,
|
||||||
|
data_export_version=3,
|
||||||
|
),
|
||||||
**force_close_template_context(
|
**force_close_template_context(
|
||||||
FORCE_CLOSE_ENABLED,
|
FORCE_CLOSE_ENABLED,
|
||||||
FORCE_CLOSE_BJ_HOUR,
|
FORCE_CLOSE_BJ_HOUR,
|
||||||
@@ -6717,6 +6726,12 @@ def stats_page():
|
|||||||
return render_main_page("stats")
|
return render_main_page("stats")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/settings")
|
||||||
|
@login_required
|
||||||
|
def settings_page():
|
||||||
|
return render_main_page("settings")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/account_snapshot")
|
@app.route("/api/account_snapshot")
|
||||||
@login_required
|
@login_required
|
||||||
def api_account_snapshot():
|
def api_account_snapshot():
|
||||||
@@ -8999,7 +9014,7 @@ def manual_transfer():
|
|||||||
amount = float(request.form.get("amount", "0"))
|
amount = float(request.form.get("amount", "0"))
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("划转金额格式错误")
|
flash("划转金额格式错误")
|
||||||
return redirect("/")
|
return redirect("/settings")
|
||||||
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
||||||
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
||||||
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
||||||
@@ -9014,7 +9029,7 @@ def manual_transfer():
|
|||||||
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
||||||
else:
|
else:
|
||||||
flash(f"手动划转失败:{msg}")
|
flash(f"手动划转失败:{msg}")
|
||||||
return redirect("/")
|
return redirect("/settings")
|
||||||
|
|
||||||
|
|
||||||
def _journal_ai_chart_builder(row):
|
def _journal_ai_chart_builder(row):
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
strategy_records: "/strategy/records",
|
strategy_records: "/strategy/records",
|
||||||
records: "/records",
|
records: "/records",
|
||||||
stats: "/stats",
|
stats: "/stats",
|
||||||
|
settings: "/settings",
|
||||||
};
|
};
|
||||||
|
|
||||||
let navToken = 0;
|
let navToken = 0;
|
||||||
|
|||||||
@@ -1917,3 +1917,136 @@ html[data-theme="light"] .journal-detail-img-thumb {
|
|||||||
background: #eef2f7;
|
background: #eef2f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 系统设置页 ── */
|
||||||
|
.settings-page {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-cards-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card h2 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card-desc,
|
||||||
|
.settings-env-hint,
|
||||||
|
.settings-transfer-auto {
|
||||||
|
color: var(--muted, #8892b0);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-live-status {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-status-reason,
|
||||||
|
.settings-policy-note {
|
||||||
|
color: var(--muted, #8892b0);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--border-soft, #2a3150);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
color: #cfd3ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-kv {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-kv-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 9.5em 1fr;
|
||||||
|
gap: 8px 12px;
|
||||||
|
padding: 6px 0;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
border-bottom: 1px dashed rgba(136, 146, 176, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-kv-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-kv-row dt {
|
||||||
|
margin: 0;
|
||||||
|
color: #9aa3c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-kv-row dd {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-kv-value {
|
||||||
|
color: #e8ecff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-kv-note {
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
color: #8892b0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-link-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-export-link {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-soft, #2a3150);
|
||||||
|
background: var(--inset-surface, #12151f);
|
||||||
|
color: #8fc8ff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-export-link:hover {
|
||||||
|
border-color: #3d4f7a;
|
||||||
|
background: #1a2030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-transfer-form {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .settings-section {
|
||||||
|
border-top-color: #d0dae4;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .settings-section h3,
|
||||||
|
html[data-theme="light"] .settings-kv-value {
|
||||||
|
color: #142232;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="light"] .settings-export-link {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #b8c8d8;
|
||||||
|
color: #1d4f8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ def embed_render_plan(page: str, embed_mode: str | None) -> EmbedRenderPlan:
|
|||||||
)
|
)
|
||||||
is_shell = embed_mode == "shell"
|
is_shell = embed_mode == "shell"
|
||||||
is_strategy = page in EMBED_STRATEGY_PAGES
|
is_strategy = page in EMBED_STRATEGY_PAGES
|
||||||
|
is_settings = page == "settings"
|
||||||
return EmbedRenderPlan(
|
return EmbedRenderPlan(
|
||||||
exchange_capitals=is_shell,
|
exchange_capitals=is_shell,
|
||||||
records_rows=page == "records",
|
records_rows=page == "records",
|
||||||
records_summary=is_shell and page != "records",
|
records_summary=is_shell and page != "records" and not is_settings,
|
||||||
key_history=page == "key_monitor",
|
key_history=page == "key_monitor",
|
||||||
key_list=page in ("key_monitor", "trade") or is_strategy,
|
key_list=page in ("key_monitor", "trade") or is_strategy,
|
||||||
orders=page == "trade" or is_strategy,
|
orders=page == "trade" or is_strategy,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ EMBED_TABS: tuple[str, ...] = (
|
|||||||
"strategy_records",
|
"strategy_records",
|
||||||
"records",
|
"records",
|
||||||
"stats",
|
"stats",
|
||||||
|
"settings",
|
||||||
)
|
)
|
||||||
|
|
||||||
PATH_TO_EMBED_TAB: dict[str, str] = {
|
PATH_TO_EMBED_TAB: dict[str, str] = {
|
||||||
@@ -29,6 +30,7 @@ PATH_TO_EMBED_TAB: dict[str, str] = {
|
|||||||
"/strategy/records": "strategy_records",
|
"/strategy/records": "strategy_records",
|
||||||
"/records": "records",
|
"/records": "records",
|
||||||
"/stats": "stats",
|
"/stats": "stats",
|
||||||
|
"/settings": "settings",
|
||||||
}
|
}
|
||||||
|
|
||||||
ORDER_RULE_TIPS_BY_EXCHANGE: dict[str, str] = {
|
ORDER_RULE_TIPS_BY_EXCHANGE: dict[str, str] = {
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
"""实例「系统设置」页:从 .env 汇总风控说明(三所共用)。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from lib.key_monitor.key_auto_order_lib import load_key_auto_order_enabled
|
||||||
|
from lib.trade.account_risk_lib import (
|
||||||
|
cooling_hours_manual,
|
||||||
|
cooling_hours_manual_journal,
|
||||||
|
manual_close_daily_limit,
|
||||||
|
max_active_positions_from_env,
|
||||||
|
mood_issues_daily_freeze_enabled,
|
||||||
|
risk_control_enabled,
|
||||||
|
)
|
||||||
|
from lib.trade.position_sizing_lib import is_full_margin_mode, load_position_sizing_mode, mode_label_zh
|
||||||
|
from lib.trade.trade_policy_lib import TradePolicy
|
||||||
|
|
||||||
|
|
||||||
|
def _env_bool(key: str, default: bool = False) -> bool:
|
||||||
|
raw = (os.getenv(key) or "").strip().lower()
|
||||||
|
if not raw:
|
||||||
|
return default
|
||||||
|
return raw in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
|
||||||
|
def _env_float(key: str, default: float) -> float:
|
||||||
|
try:
|
||||||
|
return float(os.getenv(key, str(default)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _env_int(key: str, default: int) -> int:
|
||||||
|
try:
|
||||||
|
return int(os.getenv(key, str(default)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _row(label: str, value: str, note: str = "") -> dict[str, str]:
|
||||||
|
return {"label": label, "value": value, "note": note}
|
||||||
|
|
||||||
|
|
||||||
|
def _on_off(enabled: bool) -> str:
|
||||||
|
return "开启" if enabled else "关闭"
|
||||||
|
|
||||||
|
|
||||||
|
def build_instance_settings_view(
|
||||||
|
*,
|
||||||
|
exchange_key: str,
|
||||||
|
exchange_display: str,
|
||||||
|
risk_status: Optional[dict[str, Any]] = None,
|
||||||
|
trade_policy: Optional[TradePolicy] = None,
|
||||||
|
data_export_version: int = 3,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
rs = risk_status or {}
|
||||||
|
sizing_mode = load_position_sizing_mode()
|
||||||
|
key_auto = load_key_auto_order_enabled()
|
||||||
|
reset_hour = _env_int("TRADING_DAY_RESET_HOUR", 8)
|
||||||
|
hard_limit = _env_int("DAILY_OPEN_HARD_LIMIT", 0)
|
||||||
|
alert_threshold = _env_int("DAILY_OPEN_ALERT_THRESHOLD", 5)
|
||||||
|
force_close_on = _env_bool("FORCE_CLOSE_ENABLED", False)
|
||||||
|
force_close_hour = _env_int("FORCE_CLOSE_BJ_HOUR", 0)
|
||||||
|
auto_transfer_on = _env_bool("AUTO_TRANSFER_ENABLED", False)
|
||||||
|
|
||||||
|
sections: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
sections.append(
|
||||||
|
{
|
||||||
|
"title": "交易执行",
|
||||||
|
"rows": [
|
||||||
|
_row("最大同时持仓", str(max_active_positions_from_env())),
|
||||||
|
_row("计仓模式", mode_label_zh(sizing_mode)),
|
||||||
|
_row("以损定仓风险%", f"{_env_float('RISK_PERCENT', 2):g}%"),
|
||||||
|
_row("人工最低盈亏比", f">= {_env_float('MANUAL_MIN_PLANNED_RR', 1.4):g}:1"),
|
||||||
|
_row(
|
||||||
|
"交易日切点",
|
||||||
|
f"北京时间 {reset_hour}:00",
|
||||||
|
"新交易日统计与部分开仓限制以此为准",
|
||||||
|
),
|
||||||
|
_row(
|
||||||
|
"单日开仓提醒",
|
||||||
|
f"第 {alert_threshold} 次",
|
||||||
|
"达次数推送企业微信,不拦单",
|
||||||
|
),
|
||||||
|
_row(
|
||||||
|
"单日开仓硬上限",
|
||||||
|
str(hard_limit) if hard_limit > 0 else "未启用",
|
||||||
|
"达上限后禁止一切新开仓直至下一交易日" if hard_limit > 0 else "",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sections.append(
|
||||||
|
{
|
||||||
|
"title": "账户冷静期",
|
||||||
|
"rows": [
|
||||||
|
_row("风控总开关", _on_off(risk_control_enabled())),
|
||||||
|
_row("手动平仓冷静", f"{cooling_hours_manual():g} 小时"),
|
||||||
|
_row("复盘后冷静", f"{cooling_hours_manual_journal():g} 小时", "手动平仓且填写说明后可缩短"),
|
||||||
|
_row("日手动平仓上限", f"{manual_close_daily_limit()} 次", "超限当日冻结"),
|
||||||
|
_row(
|
||||||
|
"复盘情绪日冻结",
|
||||||
|
_on_off(mood_issues_daily_freeze_enabled()),
|
||||||
|
"复盘勾选心态标签可触发当日冻结",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
key_rows = [
|
||||||
|
_row("关键位自动单", _on_off(key_auto)),
|
||||||
|
_row("关键位最低盈亏比", f"> {_env_float('KEY_AUTO_MIN_PLANNED_RR', 1.5):g}:1"),
|
||||||
|
]
|
||||||
|
if is_full_margin_mode(sizing_mode):
|
||||||
|
key_rows.append(
|
||||||
|
_row(
|
||||||
|
"全仓模式",
|
||||||
|
"仅触价类自动单",
|
||||||
|
"箱体/斐波等自动开仓在全仓下禁用",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sections.append({"title": "关键位与自动单", "rows": key_rows})
|
||||||
|
|
||||||
|
if force_close_on or (exchange_key or "").strip().lower() == "gate":
|
||||||
|
sections.append(
|
||||||
|
{
|
||||||
|
"title": "整点强制清仓",
|
||||||
|
"rows": [
|
||||||
|
_row("强制清仓", _on_off(force_close_on)),
|
||||||
|
_row("执行时刻", f"北京时间 {force_close_hour}:00 起 15 分钟内"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
policy_note = ""
|
||||||
|
if trade_policy and getattr(trade_policy, "badge_text", ""):
|
||||||
|
policy_note = str(trade_policy.badge_text)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"exchange_display": exchange_display,
|
||||||
|
"risk_status_label": str(rs.get("status_label") or "正常"),
|
||||||
|
"risk_status_reason": str(rs.get("reason") or "").strip(),
|
||||||
|
"can_trade": bool(rs.get("can_trade", True)),
|
||||||
|
"trade_policy_note": policy_note,
|
||||||
|
"sections": sections,
|
||||||
|
"data_export_version": int(data_export_version),
|
||||||
|
"show_transfer": (exchange_key or "").strip().lower() in ("gate", "binance", "okx"),
|
||||||
|
"auto_transfer_enabled": auto_transfer_on,
|
||||||
|
"auto_transfer_bj_hour": _env_int("AUTO_TRANSFER_BJ_HOUR", 8),
|
||||||
|
"auto_transfer_amount": _env_float("AUTO_TRANSFER_AMOUNT", 30),
|
||||||
|
"auto_transfer_from": (os.getenv("AUTO_TRANSFER_FROM") or "funding").strip(),
|
||||||
|
"auto_transfer_to": (os.getenv("AUTO_TRANSFER_TO") or "swap").strip(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def settings_page_context(page: str, **kwargs: Any) -> dict[str, Any]:
|
||||||
|
if (page or "").strip() != "settings":
|
||||||
|
return {}
|
||||||
|
return {"instance_settings": build_instance_settings_view(**kwargs)}
|
||||||
@@ -390,6 +390,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if page == 'settings' %}
|
||||||
|
{% include 'settings_panel.html' %}
|
||||||
|
{% endif %}
|
||||||
{% if page == 'stats' %}
|
{% if page == 'stats' %}
|
||||||
<div class="card stats-card full" id="stats-card">
|
<div class="card stats-card full" id="stats-card">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=53">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=54">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/records" data-embed-tab="records" class="{% if initial_tab == 'records' %}active{% endif %}">交易记录与复盘</a>
|
<a href="/records" data-embed-tab="records" class="{% if initial_tab == 'records' %}active{% endif %}">交易记录与复盘</a>
|
||||||
<a href="/stats" data-embed-tab="stats" class="{% if initial_tab == 'stats' %}active{% endif %}">统计分析</a>
|
<a href="/stats" data-embed-tab="stats" class="{% if initial_tab == 'stats' %}active{% endif %}">统计分析</a>
|
||||||
|
<a href="/settings" data-embed-tab="settings" class="{% if initial_tab == 'settings' %}active{% endif %}">系统设置</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
||||||
|
|
||||||
@@ -76,13 +77,6 @@
|
|||||||
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
|
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
|
||||||
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
|
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="export-bar instance-desktop-only">
|
|
||||||
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列,复盘单独导出):</span>
|
|
||||||
<a href="/export/trade_records">交易记录</a>
|
|
||||||
<a href="/export/journal_entries">复盘记录</a>
|
|
||||||
<a href="/export/key_monitors">关键位(当前)</a>
|
|
||||||
<a href="/export/key_monitor_history">关键位历史</a>
|
|
||||||
</div>
|
|
||||||
<div class="stat-box instance-desktop-only">
|
<div class="stat-box instance-desktop-only">
|
||||||
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
||||||
<div class="stat-item"><div class="label">总交易</div><div class="value" id="stat-total">{{ total }}</div></div>
|
<div class="stat-item"><div class="label">总交易</div><div class="value" id="stat-total">{{ total }}</div></div>
|
||||||
@@ -91,7 +85,7 @@
|
|||||||
<div class="stat-item"><div class="label">交易日</div><div class="value">{{ trading_day }}</div></div>
|
<div class="stat-item"><div class="label">交易日</div><div class="value">{{ trading_day }}</div></div>
|
||||||
<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>
|
||||||
{% if include_transfer_block %}
|
{% if initial_tab != 'settings' and include_transfer_block %}
|
||||||
{% include 'instance_top_bar.html' %}
|
{% include 'instance_top_bar.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -135,6 +129,6 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
|
|||||||
<script src="/static/strategy_roll.js?v=6"></script>
|
<script src="/static/strategy_roll.js?v=6"></script>
|
||||||
<script src="/static/key_monitor_form.js?v=2"></script>
|
<script src="/static/key_monitor_form.js?v=2"></script>
|
||||||
{% include 'embed_boot_scripts.html' %}
|
{% include 'embed_boot_scripts.html' %}
|
||||||
<script src="/static/instance_embed.js?v=6"></script>
|
<script src="/static/instance_embed.js?v=7"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=1">
|
<link rel="stylesheet" href="/static/instance_page.css?v=1">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=53">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=54">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -81,9 +81,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/records" class="{% if page == 'records' %}active{% endif %}">交易记录与复盘</a>
|
<a href="/records" class="{% if page == 'records' %}active{% endif %}">交易记录与复盘</a>
|
||||||
<a href="/stats" class="{% if page == 'stats' %}active{% endif %}">统计分析</a>
|
<a href="/stats" class="{% if page == 'stats' %}active{% endif %}">统计分析</a>
|
||||||
|
<a href="/settings" class="{% if page == 'settings' %}active{% endif %}">系统设置</a>
|
||||||
</div>
|
</div>
|
||||||
{% with msg=get_flashed_messages() %}{% if msg %}<div class="flash">{{ msg[0] }}</div>{% endif %}{% endwith %}
|
{% with msg=get_flashed_messages() %}{% if msg %}<div class="flash">{{ msg[0] }}</div>{% endif %}{% endwith %}
|
||||||
|
|
||||||
|
{% if page != 'settings' %}
|
||||||
<div class="list-window-bar">
|
<div class="list-window-bar">
|
||||||
<span style="color:#cfd3ef">列表筛选(<strong>UTC</strong>,默认当日):{{ list_window.label }}</span>
|
<span style="color:#cfd3ef">列表筛选(<strong>UTC</strong>,默认当日):{{ list_window.label }}</span>
|
||||||
<label>预设
|
<label>预设
|
||||||
@@ -101,13 +103,6 @@
|
|||||||
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
|
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
|
||||||
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
|
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="export-bar instance-desktop-only">
|
|
||||||
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列,复盘单独导出):</span>
|
|
||||||
<a href="/export/trade_records">交易记录</a>
|
|
||||||
<a href="/export/journal_entries">复盘记录</a>
|
|
||||||
<a href="/export/key_monitors">关键位(当前)</a>
|
|
||||||
<a href="/export/key_monitor_history">关键位历史</a>
|
|
||||||
</div>
|
|
||||||
<div class="stat-box instance-desktop-only">
|
<div class="stat-box instance-desktop-only">
|
||||||
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
||||||
<div class="stat-item"><div class="label">总交易</div><div class="value">{{ total }}</div></div>
|
<div class="stat-item"><div class="label">总交易</div><div class="value">{{ total }}</div></div>
|
||||||
@@ -116,7 +111,10 @@
|
|||||||
<div class="stat-item"><div class="label">交易日</div><div class="value">{{ trading_day }}</div></div>
|
<div class="stat-item"><div class="label">交易日</div><div class="value">{{ trading_day }}</div></div>
|
||||||
<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>
|
||||||
|
{% endif %}
|
||||||
|
{% if page != 'settings' %}
|
||||||
{% include 'instance_top_bar.html' %}
|
{% include 'instance_top_bar.html' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{% if page == 'key_monitor' %}
|
{% if page == 'key_monitor' %}
|
||||||
@@ -506,6 +504,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if page == 'settings' %}
|
||||||
|
{% include 'settings_panel.html' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if page == 'stats' %}
|
{% if page == 'stats' %}
|
||||||
<div class="card stats-card full" id="stats-card">
|
<div class="card stats-card full" id="stats-card">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{# 三所统一顶栏:划转 + 手动划转 + 可选整点前开仓开关 #}
|
{# 三所统一顶栏:实时价 + 可选整点前开仓开关(划转已移至系统设置) #}
|
||||||
{% include 'gate_transfer_block.html' %}
|
<div class="rule-tip instance-price-bar">
|
||||||
|
实时价格更新:<span id="price-last-updated">--</span>(北京时间 UTC+8)
|
||||||
|
</div>
|
||||||
{% if ui_open_guard_enabled %}
|
{% if ui_open_guard_enabled %}
|
||||||
<div class="rule-tip" id="open-guard-bar" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
<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">
|
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;color:#cfd3ef">
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{# 系统设置 · 资金划转(三所共用) #}
|
||||||
|
<div class="settings-transfer-panel">
|
||||||
|
<p class="rule-tip settings-transfer-auto">
|
||||||
|
自动划转 <strong>{{ '开启' if auto_transfer_enabled else '关闭' }}</strong>:
|
||||||
|
每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong> 起该整点小时内尝试;
|
||||||
|
账簿按 <strong>UTC 自然日</strong> 去重;
|
||||||
|
将 <code>{{ auto_transfer_to }}</code> 调整至 <strong>{{ transfer_amount_fmt|default(funds_fmt(auto_transfer_amount)) }}U</strong>:
|
||||||
|
不足从 <code>{{ auto_transfer_from }}</code> 划入、超出划回 <code>{{ auto_transfer_from }}</code>;
|
||||||
|
<strong>持仓中不划转</strong>并微信通知。
|
||||||
|
</p>
|
||||||
|
<form action="/manual_transfer" method="post" class="form-row gate-transfer-form settings-transfer-form">
|
||||||
|
<input name="amount" type="number" min="0.01" step="0.01" placeholder="手动划转金额 U" required>
|
||||||
|
<select name="from_account" aria-label="划出账户">
|
||||||
|
<option value="funding" {% if auto_transfer_from == 'funding' %}selected{% endif %}>from: funding</option>
|
||||||
|
<option value="swap" {% if auto_transfer_from == 'swap' %}selected{% endif %}>from: swap</option>
|
||||||
|
<option value="spot" {% if auto_transfer_from == 'spot' %}selected{% endif %}>from: spot</option>
|
||||||
|
</select>
|
||||||
|
<select name="to_account" aria-label="划入账户">
|
||||||
|
<option value="swap" {% if auto_transfer_to == 'swap' %}selected{% endif %}>to: swap</option>
|
||||||
|
<option value="funding" {% if auto_transfer_to == 'funding' %}selected{% endif %}>to: funding</option>
|
||||||
|
<option value="spot" {% if auto_transfer_to == 'spot' %}selected{% endif %}>to: spot</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit">执行手动划转</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{# 系统设置:风控说明 · 数据导出 · 资金划转 #}
|
||||||
|
<div class="settings-page">
|
||||||
|
<div class="settings-cards-grid">
|
||||||
|
<div class="card settings-card">
|
||||||
|
<h2>风控说明</h2>
|
||||||
|
<p class="settings-live-status">
|
||||||
|
当前账户状态:
|
||||||
|
<span class="risk-status-badge risk-status-{{ risk_status.status|default('normal') }}">
|
||||||
|
{{ instance_settings.risk_status_label }}
|
||||||
|
</span>
|
||||||
|
{% if instance_settings.risk_status_reason %}
|
||||||
|
<span class="settings-status-reason">{{ instance_settings.risk_status_reason }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% if instance_settings.trade_policy_note %}
|
||||||
|
<p class="settings-policy-note">账户限制:{{ instance_settings.trade_policy_note }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="settings-env-hint">以下参数读取自本实例 <code>.env</code>,修改后需重启进程生效。</p>
|
||||||
|
{% for section in instance_settings.sections %}
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>{{ section.title }}</h3>
|
||||||
|
<dl class="settings-kv">
|
||||||
|
{% for row in section.rows %}
|
||||||
|
<div class="settings-kv-row">
|
||||||
|
<dt>{{ row.label }}</dt>
|
||||||
|
<dd>
|
||||||
|
<span class="settings-kv-value">{{ row.value }}</span>
|
||||||
|
{% if row.note %}
|
||||||
|
<span class="settings-kv-note">{{ row.note }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card settings-card">
|
||||||
|
<h2>数据导出</h2>
|
||||||
|
<p class="settings-card-desc">
|
||||||
|
CSV · UTF-8 · v{{ instance_settings.data_export_version }}。
|
||||||
|
交易记录含开仓类型列;复盘单独导出。
|
||||||
|
</p>
|
||||||
|
<div class="settings-link-list">
|
||||||
|
<a class="settings-export-link" href="/export/trade_records">交易记录</a>
|
||||||
|
<a class="settings-export-link" href="/export/journal_entries">复盘记录</a>
|
||||||
|
<a class="settings-export-link" href="/export/key_monitors">关键位(当前)</a>
|
||||||
|
<a class="settings-export-link" href="/export/key_monitor_history">关键位历史</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if instance_settings.show_transfer %}
|
||||||
|
<div class="card settings-card">
|
||||||
|
<h2>资金划转</h2>
|
||||||
|
<p class="settings-card-desc">手动在资金账户与交易账户之间划转 USDT;自动划转规则见下方说明。</p>
|
||||||
|
{% include 'instance_transfer_panel.html' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -28,6 +28,8 @@ def test_embed_tabs_cover_main_nav():
|
|||||||
assert "trade" in EMBED_TABS
|
assert "trade" in EMBED_TABS
|
||||||
assert "key_monitor" in EMBED_TABS
|
assert "key_monitor" in EMBED_TABS
|
||||||
assert "records" in EMBED_TABS
|
assert "records" in EMBED_TABS
|
||||||
|
assert "settings" in EMBED_TABS
|
||||||
|
assert path_to_embed_tab("/settings") == "settings"
|
||||||
|
|
||||||
|
|
||||||
def test_embed_context_extras_unified_ui_flags():
|
def test_embed_context_extras_unified_ui_flags():
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""instance_settings_lib 单元测试。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from lib.instance.instance_settings_lib import build_instance_settings_view
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceSettingsLibTest(unittest.TestCase):
|
||||||
|
def test_build_settings_view_sections(self):
|
||||||
|
view = build_instance_settings_view(
|
||||||
|
exchange_key="gate",
|
||||||
|
exchange_display="Gate.io",
|
||||||
|
risk_status={"status_label": "正常", "can_trade": True},
|
||||||
|
data_export_version=3,
|
||||||
|
)
|
||||||
|
titles = [s["title"] for s in view["sections"]]
|
||||||
|
self.assertIn("交易执行", titles)
|
||||||
|
self.assertIn("账户冷静期", titles)
|
||||||
|
self.assertEqual(view["data_export_version"], 3)
|
||||||
|
self.assertTrue(view["show_transfer"])
|
||||||
|
|
||||||
|
def test_force_close_section_when_enabled(self):
|
||||||
|
old = os.environ.get("FORCE_CLOSE_ENABLED")
|
||||||
|
try:
|
||||||
|
os.environ["FORCE_CLOSE_ENABLED"] = "true"
|
||||||
|
view = build_instance_settings_view(
|
||||||
|
exchange_key="gate",
|
||||||
|
exchange_display="Gate.io",
|
||||||
|
risk_status={},
|
||||||
|
)
|
||||||
|
titles = [s["title"] for s in view["sections"]]
|
||||||
|
self.assertIn("整点强制清仓", titles)
|
||||||
|
finally:
|
||||||
|
if old is None:
|
||||||
|
os.environ.pop("FORCE_CLOSE_ENABLED", None)
|
||||||
|
else:
|
||||||
|
os.environ["FORCE_CLOSE_ENABLED"] = old
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user