fix: 滚仓拒绝原因页内展示,斐波/突破隐藏预览按钮

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 23:16:59 +08:00
parent 5a887de6f4
commit 6352fa6be3
8 changed files with 176 additions and 61 deletions
+1 -1
View File
@@ -834,7 +834,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script src="/static/manual_order_rr_preview.js?v=4"></script> <script src="/static/manual_order_rr_preview.js?v=4"></script>
<script src="/static/strategy_roll.js?v=3"></script> <script src="/static/strategy_roll.js?v=4"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+1 -1
View File
@@ -801,7 +801,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script src="/static/manual_order_rr_preview.js?v=4"></script> <script src="/static/manual_order_rr_preview.js?v=4"></script>
<script src="/static/strategy_roll.js?v=3"></script> <script src="/static/strategy_roll.js?v=4"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+1 -1
View File
@@ -801,7 +801,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script src="/static/manual_order_rr_preview.js?v=4"></script> <script src="/static/manual_order_rr_preview.js?v=4"></script>
<script src="/static/strategy_roll.js?v=3"></script> <script src="/static/strategy_roll.js?v=4"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+1 -1
View File
@@ -830,7 +830,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script src="/static/manual_order_rr_preview.js?v=4"></script> <script src="/static/manual_order_rr_preview.js?v=4"></script>
<script src="/static/strategy_roll.js?v=3"></script> <script src="/static/strategy_roll.js?v=4"></script>
<script> <script>
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }}; const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }}; const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
+1 -1
View File
@@ -114,7 +114,7 @@
<script src="/static/ai_review_render.js?v=2"></script> <script src="/static/ai_review_render.js?v=2"></script>
<script src="/static/form_submit_guard.js?v=2"></script> <script src="/static/form_submit_guard.js?v=2"></script>
<script src="/static/manual_order_rr_preview.js?v=4"></script> <script src="/static/manual_order_rr_preview.js?v=4"></script>
<script src="/static/strategy_roll.js?v=3"></script> <script src="/static/strategy_roll.js?v=4"></script>
<script src="/static/key_monitor_form.js?v=1"></script> <script src="/static/key_monitor_form.js?v=1"></script>
{% include 'embed_boot_scripts.html' %} {% include 'embed_boot_scripts.html' %}
<script src="/static/instance_embed.js?v=4"></script> <script src="/static/instance_embed.js?v=4"></script>
+24
View File
@@ -1305,6 +1305,12 @@ html[data-theme="light"] .detail-actions {
align-items: center; align-items: center;
} }
#roll-form[data-add-mode="fib_618"] #roll-preview-btn,
#roll-form[data-add-mode="fib_786"] #roll-preview-btn,
#roll-form[data-add-mode="breakout"] #roll-preview-btn {
display: none !important;
}
#strategy-roll-panel .roll-risk-banner { #strategy-roll-panel .roll-risk-banner {
margin-bottom: 8px; margin-bottom: 8px;
color: #8fc8ff; color: #8fc8ff;
@@ -1341,12 +1347,30 @@ html[data-theme="light"] #strategy-roll-panel .roll-section-title {
color: #dde2ff; color: #dde2ff;
} }
#roll-preview-box.roll-preview-box.is-error {
border-color: #8a3a4a;
background: #1a1218;
color: #ffb4b4;
}
#roll-preview-box.roll-preview-box.is-preview {
border-color: #3a5a8a;
background: #141a28;
color: #dde2ff;
}
html[data-theme="light"] #roll-preview-box.roll-preview-box { html[data-theme="light"] #roll-preview-box.roll-preview-box {
background: #f6f9fc !important; background: #f6f9fc !important;
border-color: #b8c8d8 !important; border-color: #b8c8d8 !important;
color: #1a2838 !important; color: #1a2838 !important;
} }
html[data-theme="light"] #roll-preview-box.roll-preview-box.is-error {
background: #fff5f5 !important;
border-color: #d8a0a8 !important;
color: #8a2030 !important;
}
#roll-countdown.roll-countdown { #roll-countdown.roll-countdown {
margin-top: 6px; margin-top: 6px;
color: #ffb347; color: #ffb347;
+142 -51
View File
@@ -42,10 +42,21 @@
const previewBox = document.getElementById("roll-preview-box"); const previewBox = document.getElementById("roll-preview-box");
const previewText = document.getElementById("roll-preview-text"); const previewText = document.getElementById("roll-preview-text");
const countdownEl = document.getElementById("roll-countdown"); const countdownEl = document.getElementById("roll-countdown");
const trendLocked = submitBtn && submitBtn.getAttribute("data-trend-locked") === "1";
let countdownTimer = null; let countdownTimer = null;
let previewOk = false; let previewOk = false;
let lastPreviewMode = ""; let lastPreviewMode = "";
let allowMonitorSubmit = false;
function isMarketMode() {
return (modeSel.value || "market") === "market";
}
function isMonitorMode() {
const m = modeSel.value || "market";
return m === "fib_618" || m === "fib_786" || m === "breakout";
}
function selectedOption() { function selectedOption() {
return symbolSel.options[symbolSel.selectedIndex]; return symbolSel.options[symbolSel.selectedIndex];
@@ -64,6 +75,57 @@
"当前风险:" + rp + "%(来自监控单 #" + (opt.getAttribute("data-monitor-id") || "?") + ""; "当前风险:" + rp + "%(来自监控单 #" + (opt.getAttribute("data-monitor-id") || "?") + "";
} }
function syncSubmitButton() {
if (!submitBtn || trendLocked) return;
if (isMonitorMode()) {
submitBtn.disabled = false;
return;
}
submitBtn.disabled = !previewOk || !!countdownTimer;
}
function clearMessageBox() {
if (!previewBox) return;
previewBox.style.display = "none";
previewBox.classList.remove("is-error", "is-preview");
if (previewText) previewText.textContent = "";
if (countdownEl) countdownEl.style.display = "none";
}
function showReject(msg) {
if (!previewBox || !previewText) return;
previewBox.style.display = "block";
previewBox.classList.remove("is-preview");
previewBox.classList.add("is-error");
previewText.textContent = msg || "无法执行";
if (countdownEl) countdownEl.style.display = "none";
}
function showPreviewResult(p) {
if (!previewBox || !previewText) return;
previewBox.style.display = "block";
previewBox.classList.remove("is-error");
previewBox.classList.add("is-preview");
previewText.innerHTML =
"<strong>" +
(p.add_mode_label || "") +
"</strong> · 约 <strong>" +
(p.add_amount_display != null ? p.add_amount_display : p.add_amount_raw) +
"</strong> 张<br>" +
"加仓参考价 " +
(p.add_price_display != null ? p.add_price_display : p.add_price) +
" · 新止损 " +
(p.new_sl_display != null ? p.new_sl_display : p.new_stop_loss) +
"<br>" +
"合并均价 " +
p.avg_entry_after +
" · 打到止损约 " +
p.loss_at_sl_usdt +
"U(风险预算 " +
(p.risk_budget_usdt != null ? p.risk_budget_usdt : "—") +
"U";
}
function syncFieldVisibility() { function syncFieldVisibility() {
syncRollFormMode(form, modeSel.value || "market"); syncRollFormMode(form, modeSel.value || "market");
resetPreview(); resetPreview();
@@ -71,13 +133,13 @@
function resetPreview() { function resetPreview() {
previewOk = false; previewOk = false;
if (submitBtn) submitBtn.disabled = true; allowMonitorSubmit = false;
if (previewBox) previewBox.style.display = "none"; clearMessageBox();
if (countdownEl) countdownEl.style.display = "none";
if (countdownTimer) { if (countdownTimer) {
clearInterval(countdownTimer); clearInterval(countdownTimer);
countdownTimer = null; countdownTimer = null;
} }
syncSubmitButton();
} }
function formPayload() { function formPayload() {
@@ -89,78 +151,98 @@
return obj; return obj;
} }
function runPreview() { function requestPreview() {
resetPreview(); return fetch("/strategy/roll/preview", {
if (!symbolSel.value) {
alert("请先选择持仓币种");
return;
}
previewBtn.disabled = true;
fetch("/strategy/roll/preview", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(formPayload()), body: JSON.stringify(formPayload()),
credentials: "same-origin", credentials: "same-origin",
}) }).then(function (r) {
.then(function (r) { return r.json();
return r.json(); });
}) }
function runPreview() {
resetPreview();
if (!symbolSel.value) {
showReject("请先选择持仓币种");
return;
}
if (previewBtn) previewBtn.disabled = true;
requestPreview()
.then(function (data) { .then(function (data) {
previewBtn.disabled = false; if (previewBtn) previewBtn.disabled = false;
if (!data.ok) { if (!data.ok) {
alert(data.msg || "预览失败"); showReject(data.msg || "预览失败");
return; return;
} }
const p = data.preview || {}; const p = data.preview || {};
lastPreviewMode = p.add_mode || modeSel.value; lastPreviewMode = p.add_mode || modeSel.value;
previewText.innerHTML = showPreviewResult(p);
"<strong>" +
(p.add_mode_label || "") +
"</strong> · 约 <strong>" +
(p.add_amount_display != null ? p.add_amount_display : p.add_amount_raw) +
"</strong> 张<br>" +
"加仓参考价 " +
(p.add_price_display != null ? p.add_price_display : p.add_price) +
" · 新止损 " +
(p.new_sl_display != null ? p.new_sl_display : p.new_stop_loss) +
"<br>" +
"合并均价 " +
p.avg_entry_after +
" · 打到止损约 " +
p.loss_at_sl_usdt +
"U(风险预算 " +
(p.risk_budget_usdt != null ? p.risk_budget_usdt : "—") +
"U";
previewBox.style.display = "block";
previewOk = true; previewOk = true;
if (lastPreviewMode === "market") { if (lastPreviewMode === "market") {
startCountdown(10); startCountdown(10);
} else if (submitBtn) { } else {
submitBtn.disabled = false; syncSubmitButton();
countdownEl.style.display = "none";
} }
}) })
.catch(function () { .catch(function () {
previewBtn.disabled = false; if (previewBtn) previewBtn.disabled = false;
alert("预览请求失败"); showReject("预览请求失败,请稍后重试");
});
}
function runMonitorSubmit() {
if (!symbolSel.value) {
showReject("请先选择持仓币种");
return;
}
if (submitBtn) submitBtn.disabled = true;
requestPreview()
.then(function (data) {
if (submitBtn && !trendLocked) submitBtn.disabled = false;
if (!data.ok) {
showReject(data.msg || "无法提交监控");
return;
}
const p = data.preview || {};
const modeLabel = modeSel.options[modeSel.selectedIndex].text;
const summary =
"约 " +
(p.add_amount_display != null ? p.add_amount_display : p.add_amount_raw) +
" 张 · 触发参考价 " +
(p.add_price_display != null ? p.add_price_display : p.add_price) +
" · 新止损 " +
(p.new_sl_display != null ? p.new_sl_display : p.new_stop_loss);
if (!confirm("确认提交「" + modeLabel + "」?\n" + summary)) {
return;
}
allowMonitorSubmit = true;
form.requestSubmit();
})
.catch(function () {
if (submitBtn && !trendLocked) submitBtn.disabled = false;
showReject("校验请求失败,请稍后重试");
}); });
} }
function startCountdown(sec) { function startCountdown(sec) {
let left = sec; let left = sec;
if (submitBtn) submitBtn.disabled = true; if (submitBtn) submitBtn.disabled = true;
countdownEl.style.display = "block"; if (countdownEl) {
countdownEl.textContent = "市价加仓:" + left + " 秒后可执行(可取消刷新预览)"; countdownEl.style.display = "block";
countdownEl.textContent = "市价加仓:" + left + " 秒后可执行(修改表单将取消预览)";
}
countdownTimer = setInterval(function () { countdownTimer = setInterval(function () {
left -= 1; left -= 1;
if (left <= 0) { if (left <= 0) {
clearInterval(countdownTimer); clearInterval(countdownTimer);
countdownTimer = null; countdownTimer = null;
countdownEl.textContent = "可以执行市价加仓"; if (countdownEl) countdownEl.textContent = "可以执行市价加仓";
if (submitBtn) submitBtn.disabled = false; syncSubmitButton();
return; return;
} }
countdownEl.textContent = "市价加仓:" + left + " 秒后可执行"; if (countdownEl) countdownEl.textContent = "市价加仓:" + left + " 秒后可执行";
}, 1000); }, 1000);
} }
@@ -173,16 +255,25 @@
form.addEventListener("change", function (e) { form.addEventListener("change", function (e) {
if (e.target !== previewBtn) resetPreview(); if (e.target !== previewBtn) resetPreview();
}); });
previewBtn.addEventListener("click", runPreview); if (previewBtn) previewBtn.addEventListener("click", runPreview);
form.addEventListener("submit", function (e) { form.addEventListener("submit", function (e) {
if (!previewOk) { if (isMonitorMode()) {
if (allowMonitorSubmit) {
allowMonitorSubmit = false;
return;
}
e.preventDefault(); e.preventDefault();
alert("请先点击预览"); runMonitorSubmit();
return; return;
} }
if (lastPreviewMode === "market" && submitBtn && submitBtn.disabled) { if (!previewOk) {
e.preventDefault(); e.preventDefault();
alert("请等待 10 秒确认倒计时结束"); showReject("请先点击「预览」并通过校验");
return;
}
if (submitBtn && submitBtn.disabled) {
e.preventDefault();
showReject("请等待 10 秒确认倒计时结束后再执行市价加仓");
return; return;
} }
const modeLabel = modeSel.options[modeSel.selectedIndex].text; const modeLabel = modeSel.options[modeSel.selectedIndex].text;
+5 -5
View File
@@ -6,7 +6,7 @@
<strong>仅人工提交</strong>;须先在「实盘下单」有同向持仓。仅<strong>以损定仓</strong>模式可用。<br> <strong>仅人工提交</strong>;须先在「实盘下单」有同向持仓。仅<strong>以损定仓</strong>模式可用。<br>
做多/做空各最多滚仓 <strong>3</strong> 次(仅计已成交腿);止盈<strong>锁定首仓</strong>不变。<br> 做多/做空各最多滚仓 <strong>3</strong> 次(仅计已成交腿);止盈<strong>锁定首仓</strong>不变。<br>
风险比例读取所选监控单,<strong>不可手改</strong>;打到新止损时合并持仓亏损 ≈ 1 个风险单位(当前基数 × 监控 risk%)。<br> 风险比例读取所选监控单,<strong>不可手改</strong>;打到新止损时合并持仓亏损 ≈ 1 个风险单位(当前基数 × 监控 risk%)。<br>
斐波/突破为<strong>程序监控</strong>(mark 价穿越触发),触价后市价加仓;同时仅允许 <strong>1</strong> 条监控中腿,提交后<strong>不可修改</strong>,可删除。<br> 斐波/突破为<strong>程序监控</strong>(mark 价穿越触发),触价后市价加仓;填写后直接点「执行滚仓」(无需预览)。同时仅允许 <strong>1</strong> 条监控中腿,提交后<strong>不可修改</strong>,可删除。<br>
手动平仓后滚仓监控自动结束;<strong>已成交腿历史保留</strong>供复盘。<br> 手动平仓后滚仓监控自动结束;<strong>已成交腿历史保留</strong>供复盘。<br>
<a href="/strategy/roll/docs" target="_blank" rel="noopener" class="roll-doc-link">→ 顺势加仓完整逻辑说明</a><br> <a href="/strategy/roll/docs" target="_blank" rel="noopener" class="roll-doc-link">→ 顺势加仓完整逻辑说明</a><br>
{% if roll_trend_active %}<span style="color:#ff8f8f">当前有运行中的趋势回调计划,请先结束后再滚仓。</span>{% endif %} {% if roll_trend_active %}<span style="color:#ff8f8f">当前有运行中的趋势回调计划,请先结束后再滚仓。</span>{% endif %}
@@ -44,11 +44,11 @@
<input name="breakthrough_price" id="roll-breakout" step="any" placeholder="突破价"> <input name="breakthrough_price" id="roll-breakout" step="any" placeholder="突破价">
</span> </span>
<input name="new_stop_loss" id="roll-stop-loss" type="number" min="0" step="any" placeholder="新止损价" required> <input name="new_stop_loss" id="roll-stop-loss" type="number" min="0" step="any" placeholder="新止损价" required>
<button type="button" id="roll-preview-btn" {% if roll_trend_active %}disabled{% endif %}>预览</button> <button type="button" id="roll-preview-btn" class="roll-preview-btn" {% if roll_trend_active %}disabled{% endif %}>预览</button>
<button type="submit" id="roll-submit-btn" {% if roll_trend_active %}disabled style="opacity:.5"{% endif %} disabled>执行滚仓</button> <button type="submit" id="roll-submit-btn" {% if roll_trend_active %}disabled style="opacity:.5" data-trend-locked="1"{% endif %} disabled>执行滚仓</button>
</form> </form>
<div id="roll-preview-box" class="rule-tip roll-preview-box" style="display:none"> <div id="roll-preview-box" class="rule-tip roll-preview-box" style="display:none" role="status" aria-live="polite">
<div id="roll-preview-text"></div> <div id="roll-preview-text"></div>
<div id="roll-countdown" class="roll-countdown" style="display:none"></div> <div id="roll-countdown" class="roll-countdown" style="display:none"></div>
</div> </div>
@@ -101,4 +101,4 @@
</table> </table>
</div> </div>
</div> </div>
<script src="/static/strategy_roll.js?v=3"></script> <script src="/static/strategy_roll.js?v=4"></script>