Show options-page price clock and drive header trade stats from pure options.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5134,6 +5134,16 @@ def render_main_page(page="options", embed_mode=None):
|
|||||||
records = []
|
records = []
|
||||||
total = rate = 0
|
total = rate = 0
|
||||||
profit_loss_ratio = None
|
profit_loss_ratio = None
|
||||||
|
if page == "options" and OKX_OPTIONS_ENABLED:
|
||||||
|
try:
|
||||||
|
from lib.options.options_stats_lib import header_options_stats_for_window
|
||||||
|
|
||||||
|
opt_header = header_options_stats_for_window(conn, list_window, APP_TZ)
|
||||||
|
total = opt_header["total"]
|
||||||
|
rate = opt_header["rate"]
|
||||||
|
profit_loss_ratio = opt_header.get("profit_loss_ratio")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
from lib.trade.trade_labels_lib import count_position_limit_active_monitors
|
from lib.trade.trade_labels_lib import count_position_limit_active_monitors
|
||||||
|
|
||||||
@@ -5482,6 +5492,16 @@ def api_account_snapshot():
|
|||||||
)
|
)
|
||||||
|
|
||||||
header_trade_stats = header_trade_stats_for_window(conn, _list_window_from_request(), APP_TZ)
|
header_trade_stats = header_trade_stats_for_window(conn, _list_window_from_request(), APP_TZ)
|
||||||
|
page_hint = (request.args.get("page") or "").strip().lower()
|
||||||
|
if page_hint == "options" and OKX_OPTIONS_ENABLED:
|
||||||
|
try:
|
||||||
|
from lib.options.options_stats_lib import header_options_stats_for_window
|
||||||
|
|
||||||
|
header_trade_stats = header_options_stats_for_window(
|
||||||
|
conn, _list_window_from_request(), APP_TZ
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
conn.close()
|
conn.close()
|
||||||
open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR
|
open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR
|
||||||
from lib.trade.open_trade_gate_lib import resolve_manual_open_gate
|
from lib.trade.open_trade_gate_lib import resolve_manual_open_gate
|
||||||
@@ -5582,6 +5602,7 @@ def api_account_snapshot():
|
|||||||
"total": header_trade_stats["total"],
|
"total": header_trade_stats["total"],
|
||||||
"rate": header_trade_stats["rate"],
|
"rate": header_trade_stats["rate"],
|
||||||
"profit_loss_ratio": header_trade_stats.get("profit_loss_ratio"),
|
"profit_loss_ratio": header_trade_stats.get("profit_loss_ratio"),
|
||||||
|
"updated_at": app_now_str(),
|
||||||
"risk_status": risk_status,
|
"risk_status": risk_status,
|
||||||
**force_close_template_context(
|
**force_close_template_context(
|
||||||
FORCE_CLOSE_ENABLED,
|
FORCE_CLOSE_ENABLED,
|
||||||
|
|||||||
@@ -356,7 +356,11 @@
|
|||||||
|
|
||||||
function syncShellChrome(tab) {
|
function syncShellChrome(tab) {
|
||||||
const hideTopBar =
|
const hideTopBar =
|
||||||
tab === "settings" || tab === "risk_policy" || tab === "system_guide" || tab === "env_config";
|
tab === "settings" ||
|
||||||
|
tab === "risk_policy" ||
|
||||||
|
tab === "system_guide" ||
|
||||||
|
tab === "env_config" ||
|
||||||
|
tab === "options_review";
|
||||||
document.querySelectorAll(".instance-top-bar").forEach((el) => {
|
document.querySelectorAll(".instance-top-bar").forEach((el) => {
|
||||||
el.hidden = hideTopBar;
|
el.hidden = hideTopBar;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
.list-item{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:9px;background:#1a2034;border:1px solid #2a3150;border-radius:8px}
|
.list-item{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:9px;background:#1a2034;border:1px solid #2a3150;border-radius:8px}
|
||||||
.btn-del{padding:5px 9px;background:#2f2134;color:#ff7b7b;border-radius:8px;text-decoration:none;font-size:.8rem}
|
.btn-del{padding:5px 9px;background:#2f2134;color:#ff7b7b;border-radius:8px;text-decoration:none;font-size:.8rem}
|
||||||
.rule-tip{font-size:.8rem;color:#95a2c2;margin-bottom:8px}
|
.rule-tip{font-size:.8rem;color:#95a2c2;margin-bottom:8px}
|
||||||
|
.instance-price-bar{display:block;visibility:visible;white-space:nowrap;overflow:visible;line-height:1.4}
|
||||||
table{width:100%;border-collapse:collapse}
|
table{width:100%;border-collapse:collapse}
|
||||||
th,td{padding:8px;text-align:left;border-bottom:1px solid #25253b;font-size:.85rem}
|
th,td{padding:8px;text-align:left;border-bottom:1px solid #25253b;font-size:.85rem}
|
||||||
th{color:#a9a9ff}
|
th{color:#a9a9ff}
|
||||||
|
|||||||
@@ -1175,6 +1175,10 @@ function accountSnapshotFundingMissing(data){
|
|||||||
let accountSnapshotRetryCount = 0;
|
let accountSnapshotRetryCount = 0;
|
||||||
function applyAccountSnapshot(data){
|
function applyAccountSnapshot(data){
|
||||||
if(!data || typeof data !== "object") return;
|
if(!data || typeof data !== "object") return;
|
||||||
|
if(data.updated_at){
|
||||||
|
const updatedEl = document.getElementById("price-last-updated");
|
||||||
|
if(updatedEl) updatedEl.innerText = data.updated_at;
|
||||||
|
}
|
||||||
if(typeof data.show_perp_funds !== "undefined"){
|
if(typeof data.show_perp_funds !== "undefined"){
|
||||||
applyPerpFundsVisibility(data.show_perp_funds);
|
applyPerpFundsVisibility(data.show_perp_funds);
|
||||||
}
|
}
|
||||||
@@ -1266,7 +1270,11 @@ function applyAccountSnapshot(data){
|
|||||||
}
|
}
|
||||||
function refreshAccountSnapshot(opts){
|
function refreshAccountSnapshot(opts){
|
||||||
const options = opts || {};
|
const options = opts || {};
|
||||||
const qs = options.force ? "?force=1" : "";
|
const params = new URLSearchParams();
|
||||||
|
if(options.force) params.set("force", "1");
|
||||||
|
const page = (document.body && document.body.getAttribute("data-page")) || "";
|
||||||
|
if(page) params.set("page", page);
|
||||||
|
const qs = params.toString() ? ("?" + params.toString()) : "";
|
||||||
fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{
|
fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{
|
||||||
applyAccountSnapshot(data);
|
applyAccountSnapshot(data);
|
||||||
if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){
|
if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<script src="/static/autofill_guard.js?v=1"></script>
|
<script src="/static/autofill_guard.js?v=1"></script>
|
||||||
<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=15">
|
<link rel="stylesheet" href="/static/instance_page.css?v=16">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=118">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=118">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<script src="/static/open_submit_gate.js?v=1"></script>
|
<script src="/static/open_submit_gate.js?v=1"></script>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
||||||
|
|
||||||
{% include 'instance_header_panel.html' %}
|
{% include 'instance_header_panel.html' %}
|
||||||
{% if initial_tab not in ('settings', 'risk_policy', 'system_guide', 'env_config') and include_transfer_block %}
|
{% if initial_tab not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options_review') and include_transfer_block %}
|
||||||
{% include 'instance_top_bar.html' %}
|
{% include 'instance_top_bar.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
|||||||
</script>
|
</script>
|
||||||
<script src="/static/instance_settings_prefs.js?v=22"></script>
|
<script src="/static/instance_settings_prefs.js?v=22"></script>
|
||||||
<script src="/static/instance_live.js?v=7"></script>
|
<script src="/static/instance_live.js?v=7"></script>
|
||||||
<script src="/static/instance_embed.js?v=31"></script>
|
<script src="/static/instance_embed.js?v=32"></script>
|
||||||
<script src="/static/instance_mobile_nav.js?v=2"></script>
|
<script src="/static/instance_mobile_nav.js?v=2"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ pwa_app_name }}</title>
|
<title>{{ pwa_app_name }}</title>
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=15">
|
<link rel="stylesheet" href="/static/instance_page.css?v=16">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=118">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=118">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
{% 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 %}
|
||||||
|
|
||||||
{% include 'instance_header_panel.html' %}
|
{% include 'instance_header_panel.html' %}
|
||||||
{% if page not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options', 'options_review', 'hedge_plan') %}
|
{% if page not in ('settings', 'risk_policy', 'system_guide', 'env_config', 'options_review') %}
|
||||||
{% include 'instance_top_bar.html' %}
|
{% include 'instance_top_bar.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -1441,6 +1441,10 @@ function accountSnapshotFundingMissing(data){
|
|||||||
let accountSnapshotRetryCount = 0;
|
let accountSnapshotRetryCount = 0;
|
||||||
function applyAccountSnapshot(data){
|
function applyAccountSnapshot(data){
|
||||||
if(!data || typeof data !== "object") return;
|
if(!data || typeof data !== "object") return;
|
||||||
|
if(data.updated_at){
|
||||||
|
const updatedEl = document.getElementById("price-last-updated");
|
||||||
|
if(updatedEl) updatedEl.innerText = data.updated_at;
|
||||||
|
}
|
||||||
if(typeof data.show_perp_funds !== "undefined"){
|
if(typeof data.show_perp_funds !== "undefined"){
|
||||||
applyPerpFundsVisibility(data.show_perp_funds);
|
applyPerpFundsVisibility(data.show_perp_funds);
|
||||||
}
|
}
|
||||||
@@ -1541,7 +1545,11 @@ function applyAccountSnapshot(data){
|
|||||||
}
|
}
|
||||||
function refreshAccountSnapshot(opts){
|
function refreshAccountSnapshot(opts){
|
||||||
const options = opts || {};
|
const options = opts || {};
|
||||||
const qs = options.force ? "?force=1" : "";
|
const params = new URLSearchParams();
|
||||||
|
if(options.force) params.set("force", "1");
|
||||||
|
const page = (document.body && document.body.getAttribute("data-page")) || "";
|
||||||
|
if(page) params.set("page", page);
|
||||||
|
const qs = params.toString() ? ("?" + params.toString()) : "";
|
||||||
fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{
|
fetch("/api/account_snapshot" + qs).then(r=>r.json()).then(data=>{
|
||||||
applyAccountSnapshot(data);
|
applyAccountSnapshot(data);
|
||||||
if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){
|
if(accountSnapshotFundingMissing(data) && !options.force && accountSnapshotRetryCount < 3){
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{# 三所统一顶栏:实时价(划转已移至系统设置;切点前开仓说明见风控说明·交易执行) #}
|
{# 三所统一顶栏:实时价(划转已移至系统设置;切点前开仓说明见风控说明·交易执行) #}
|
||||||
<div class="rule-tip instance-price-bar">
|
<div class="rule-tip instance-top-bar instance-price-bar">
|
||||||
实时价格更新:<span id="price-last-updated">--</span>(北京时间 UTC+8)
|
实时价格更新:<span id="price-last-updated">--</span>(北京时间 UTC+8)
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -113,9 +113,12 @@ def compute_options_stats(get_db) -> dict[str, Any]:
|
|||||||
SELECT created_at FROM options_trades WHERE status = 'open'
|
SELECT created_at FROM options_trades WHERE status = 'open'
|
||||||
"""
|
"""
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
return _stats_from_option_trade_rows(closed_rows, open_rows)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _stats_from_option_trade_rows(closed_rows, open_rows) -> dict[str, Any]:
|
||||||
wins: list[float] = []
|
wins: list[float] = []
|
||||||
losses: list[float] = []
|
losses: list[float] = []
|
||||||
win_holds: list[float] = []
|
win_holds: list[float] = []
|
||||||
@@ -171,3 +174,33 @@ def compute_options_stats(get_db) -> dict[str, Any]:
|
|||||||
"open_count": len(open_holds),
|
"open_count": len(open_holds),
|
||||||
"avg_open_hold_sec": _avg_seconds(open_holds),
|
"avg_open_hold_sec": _avg_seconds(open_holds),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def header_options_stats_for_window(conn, list_window: dict[str, Any], app_tz) -> dict[str, Any]:
|
||||||
|
"""顶栏:纯期权(options_trades)总交易/胜率/盈亏比,按列表窗过滤."""
|
||||||
|
from lib.common.history_window_lib import utc_window_to_bj_sql_strings
|
||||||
|
|
||||||
|
init_options_tables(conn)
|
||||||
|
start_bj, end_bj = utc_window_to_bj_sql_strings(
|
||||||
|
list_window["start_utc"], list_window["end_utc"], app_tz
|
||||||
|
)
|
||||||
|
closed_rows = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT realized_pnl, created_at, closed_at
|
||||||
|
FROM options_trades
|
||||||
|
WHERE status = 'closed'
|
||||||
|
AND realized_pnl IS NOT NULL
|
||||||
|
AND COALESCE(closed_at, created_at) >= ?
|
||||||
|
AND COALESCE(closed_at, created_at) <= ?
|
||||||
|
""",
|
||||||
|
(start_bj, end_bj),
|
||||||
|
).fetchall()
|
||||||
|
open_rows = conn.execute(
|
||||||
|
"SELECT created_at FROM options_trades WHERE status = 'open'"
|
||||||
|
).fetchall()
|
||||||
|
stats = _stats_from_option_trade_rows(closed_rows, open_rows)
|
||||||
|
return {
|
||||||
|
"total": int(stats.get("total_closed") or 0),
|
||||||
|
"rate": float(stats.get("win_rate") or 0),
|
||||||
|
"profit_loss_ratio": stats.get("profit_loss_ratio"),
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user