Unify key support/resistance monitor type and fix form parity.
Merge 关键阻力位/关键支撑位 into 关键支撑阻力, share key_monitor_form.js across hub and new-tab views, and add hub shortcut to /key_monitor. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -158,6 +158,7 @@ from key_monitor_lib import (
|
|||||||
KEY_DIRECTION_WATCH,
|
KEY_DIRECTION_WATCH,
|
||||||
KEY_MONITOR_ALERT_ONLY_TYPES,
|
KEY_MONITOR_ALERT_ONLY_TYPES,
|
||||||
KEY_MONITOR_AUTO_TYPES,
|
KEY_MONITOR_AUTO_TYPES,
|
||||||
|
KEY_MONITOR_RS_TYPE,
|
||||||
KEY_MONITOR_RS_TYPES,
|
KEY_MONITOR_RS_TYPES,
|
||||||
auto_amp_ok,
|
auto_amp_ok,
|
||||||
auto_confirm_ok,
|
auto_confirm_ok,
|
||||||
@@ -7792,6 +7793,7 @@ def add_key():
|
|||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
direction_sel = KEY_DIRECTION_WATCH
|
direction_sel = KEY_DIRECTION_WATCH
|
||||||
|
mt = KEY_MONITOR_RS_TYPE
|
||||||
elif direction_sel not in ("long", "short"):
|
elif direction_sel not in ("long", "short"):
|
||||||
flash("箱体/收敛突破请选择做多或做空")
|
flash("箱体/收敛突破请选择做多或做空")
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
@@ -7828,7 +7830,7 @@ def add_key():
|
|||||||
conn.close()
|
conn.close()
|
||||||
flash(
|
flash(
|
||||||
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
||||||
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
|
"请平仓后再试,或使用「关键支撑阻力」(仅提醒)。"
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ex_sym_key = normalize_exchange_symbol(symbol)
|
ex_sym_key = normalize_exchange_symbol(symbol)
|
||||||
@@ -8004,7 +8006,7 @@ def add_key():
|
|||||||
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
flash(
|
flash(
|
||||||
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
f"添加成功({symbol} 日成交量排名 {rank}/{total})|关键支撑阻力:双向监控上/下沿,"
|
||||||
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -629,8 +629,7 @@
|
|||||||
<select name="type" required>
|
<select name="type" required>
|
||||||
<option value="箱体突破">箱体突破</option>
|
<option value="箱体突破">箱体突破</option>
|
||||||
<option value="收敛突破">收敛突破</option>
|
<option value="收敛突破">收敛突破</option>
|
||||||
<option value="关键阻力位">关键阻力位</option>
|
<option value="关键支撑阻力">关键支撑阻力</option>
|
||||||
<option value="关键支撑位">关键支撑位</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select name="direction" required>
|
<select name="direction" required>
|
||||||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||||||
@@ -1550,120 +1549,9 @@ if(journalForm){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncKeyMonitorFormFields(){
|
|
||||||
const typeEl = document.querySelector('#key-form [name="type"]');
|
|
||||||
const dirEl = document.getElementById("key-direction");
|
|
||||||
const modeEl = document.getElementById("key-sl-tp-mode");
|
|
||||||
const manualTp = document.getElementById("key-manual-tp");
|
|
||||||
const beWrap = document.getElementById("key-breakeven-wrap");
|
|
||||||
if(!typeEl) return;
|
|
||||||
const t = (typeEl.value || "").trim();
|
|
||||||
const autoTypes = new Set(["箱体突破","收敛突破"]);
|
|
||||||
const fibTypes = new Set(["斐波回调0.618","斐波回调0.786"]);
|
|
||||||
const fbTypes = new Set(["假突破"]);
|
|
||||||
const teTypes = new Set(["触价开仓"]);
|
|
||||||
const rsTypes = new Set(["关键阻力位","关键支撑位"]);
|
|
||||||
const showAuto = autoTypes.has(t);
|
|
||||||
const showFb = fbTypes.has(t);
|
|
||||||
const showTe = teTypes.has(t);
|
|
||||||
const showBe = showAuto || fibTypes.has(t) || showFb || showTe;
|
|
||||||
const showDir = !rsTypes.has(t);
|
|
||||||
const upperEl = document.getElementById("key-upper");
|
|
||||||
const lowerEl = document.getElementById("key-lower");
|
|
||||||
const fbPriceEl = document.getElementById("key-fb-price");
|
|
||||||
const teEntryEl = document.getElementById("key-trigger-entry");
|
|
||||||
const teSlEl = document.getElementById("key-trigger-sl");
|
|
||||||
const teTpEl = document.getElementById("key-trigger-tp");
|
|
||||||
if(dirEl){
|
|
||||||
dirEl.style.display = showDir ? "" : "none";
|
|
||||||
dirEl.required = showDir;
|
|
||||||
if(!showDir) dirEl.value = "";
|
|
||||||
}
|
|
||||||
if(modeEl) modeEl.style.display = showAuto ? "" : "none";
|
|
||||||
if(manualTp){
|
|
||||||
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
|
||||||
manualTp.style.display = trend ? "" : "none";
|
|
||||||
manualTp.required = !!trend;
|
|
||||||
}
|
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
|
||||||
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
|
||||||
const hideBounds = showFb || showTe;
|
|
||||||
if(upperEl){
|
|
||||||
upperEl.style.display = hideBounds ? "none" : "";
|
|
||||||
upperEl.required = !hideBounds;
|
|
||||||
if(hideBounds) upperEl.value = "";
|
|
||||||
}
|
|
||||||
if(lowerEl){
|
|
||||||
lowerEl.style.display = hideBounds ? "none" : "";
|
|
||||||
lowerEl.required = !hideBounds;
|
|
||||||
if(hideBounds) lowerEl.value = "";
|
|
||||||
}
|
|
||||||
if(fbPriceEl){
|
|
||||||
fbPriceEl.style.display = showFb ? "" : "none";
|
|
||||||
fbPriceEl.required = showFb;
|
|
||||||
if(!showFb) fbPriceEl.value = "";
|
|
||||||
fbPriceEl.placeholder = (dirEl && dirEl.value === "short") ? "高点(阻力)" : ((dirEl && dirEl.value === "long") ? "低点(支撑)" : "做空填高点/做多填低点");
|
|
||||||
}
|
|
||||||
[teEntryEl, teSlEl, teTpEl].forEach((el)=>{
|
|
||||||
if(!el) return;
|
|
||||||
el.style.display = showTe ? "" : "none";
|
|
||||||
el.required = showTe;
|
|
||||||
if(!showTe) el.value = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const keyTypeSel = document.querySelector('#key-form [name="type"]');
|
|
||||||
const keyModeSel = document.getElementById("key-sl-tp-mode");
|
|
||||||
const keyDirSel = document.getElementById("key-direction");
|
|
||||||
if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
syncKeyMonitorFormFields();
|
|
||||||
if(window.TimeCloseUI){
|
if(window.TimeCloseUI){
|
||||||
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
|
||||||
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
|
||||||
if(keyForm){
|
|
||||||
keyForm.addEventListener("submit", (e)=>{
|
|
||||||
e.preventDefault();
|
|
||||||
if(window.FormSubmitGuard && FormSubmitGuard.isLocked(keyForm)) return;
|
|
||||||
const symbolEl = keyForm.querySelector('[name="symbol"]');
|
|
||||||
const symbol = (symbolEl ? symbolEl.value : "").trim();
|
|
||||||
if(!symbol){
|
|
||||||
alert("请先输入交易对");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const typeVal = (keyForm.querySelector('[name="type"]') || {}).value || "";
|
|
||||||
if(typeVal === "假突破"){
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.lock(keyForm, "校验排名中…");
|
|
||||||
fetch(`/api/symbol_liquidity_rank?symbol=${encodeURIComponent(symbol)}`)
|
|
||||||
.then(r=>r.json().then(d=>({status:r.status, data:d})))
|
|
||||||
.then(({status,data})=>{
|
|
||||||
if(status >= 400 || !data.ok){
|
|
||||||
alert((data && data.msg) || "日成交量排名读取失败");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rankMax = data.rank_max || 30;
|
|
||||||
if(!data.in_top30){
|
|
||||||
alert(`${data.symbol} 当前日成交量排名 ${data.rank}/${data.total},不在前${rankMax},已拦截。`);
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
})
|
|
||||||
.catch(()=>{
|
|
||||||
alert("日成交量排名检查失败,请稍后重试");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if(document.getElementById("journal-list")) loadJournals();
|
if(document.getElementById("journal-list")) loadJournals();
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ from key_monitor_lib import (
|
|||||||
KEY_DIRECTION_WATCH,
|
KEY_DIRECTION_WATCH,
|
||||||
KEY_MONITOR_ALERT_ONLY_TYPES,
|
KEY_MONITOR_ALERT_ONLY_TYPES,
|
||||||
KEY_MONITOR_AUTO_TYPES,
|
KEY_MONITOR_AUTO_TYPES,
|
||||||
|
KEY_MONITOR_RS_TYPE,
|
||||||
KEY_MONITOR_RS_TYPES,
|
KEY_MONITOR_RS_TYPES,
|
||||||
auto_amp_ok,
|
auto_amp_ok,
|
||||||
auto_confirm_ok,
|
auto_confirm_ok,
|
||||||
@@ -7684,6 +7685,7 @@ def add_key():
|
|||||||
direction_sel = (d.get("direction") or "").strip().lower()
|
direction_sel = (d.get("direction") or "").strip().lower()
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
direction_sel = KEY_DIRECTION_WATCH
|
direction_sel = KEY_DIRECTION_WATCH
|
||||||
|
mt = KEY_MONITOR_RS_TYPE
|
||||||
elif direction_sel not in ("long", "short"):
|
elif direction_sel not in ("long", "short"):
|
||||||
flash("箱体/收敛突破请选择做多或做空")
|
flash("箱体/收敛突破请选择做多或做空")
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
@@ -7723,7 +7725,7 @@ def add_key():
|
|||||||
conn = None
|
conn = None
|
||||||
flash(
|
flash(
|
||||||
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
||||||
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
|
"请平仓后再试,或使用「关键支撑阻力」(仅提醒)。"
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ex_sym_key = normalize_exchange_symbol(symbol)
|
ex_sym_key = normalize_exchange_symbol(symbol)
|
||||||
@@ -7925,7 +7927,7 @@ def add_key():
|
|||||||
extra += f"|{time_close_label(tc_h)}"
|
extra += f"|{time_close_label(tc_h)}"
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
flash(
|
flash(
|
||||||
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
f"添加成功({symbol} 日成交量排名 {rank}/{total})|关键支撑阻力:双向监控上/下沿,"
|
||||||
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -596,8 +596,7 @@
|
|||||||
<select name="type" required>
|
<select name="type" required>
|
||||||
<option value="箱体突破">箱体突破</option>
|
<option value="箱体突破">箱体突破</option>
|
||||||
<option value="收敛突破">收敛突破</option>
|
<option value="收敛突破">收敛突破</option>
|
||||||
<option value="关键阻力位">关键阻力位</option>
|
<option value="关键支撑阻力">关键支撑阻力</option>
|
||||||
<option value="关键支撑位">关键支撑位</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select name="direction" required>
|
<select name="direction" required>
|
||||||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||||||
@@ -1517,120 +1516,9 @@ if(journalForm){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncKeyMonitorFormFields(){
|
|
||||||
const typeEl = document.querySelector('#key-form [name="type"]');
|
|
||||||
const dirEl = document.getElementById("key-direction");
|
|
||||||
const modeEl = document.getElementById("key-sl-tp-mode");
|
|
||||||
const manualTp = document.getElementById("key-manual-tp");
|
|
||||||
const beWrap = document.getElementById("key-breakeven-wrap");
|
|
||||||
if(!typeEl) return;
|
|
||||||
const t = (typeEl.value || "").trim();
|
|
||||||
const autoTypes = new Set(["箱体突破","收敛突破"]);
|
|
||||||
const fibTypes = new Set(["斐波回调0.618","斐波回调0.786"]);
|
|
||||||
const fbTypes = new Set(["假突破"]);
|
|
||||||
const teTypes = new Set(["触价开仓"]);
|
|
||||||
const rsTypes = new Set(["关键阻力位","关键支撑位"]);
|
|
||||||
const showAuto = autoTypes.has(t);
|
|
||||||
const showFb = fbTypes.has(t);
|
|
||||||
const showTe = teTypes.has(t);
|
|
||||||
const showBe = showAuto || fibTypes.has(t) || showFb || showTe;
|
|
||||||
const showDir = !rsTypes.has(t);
|
|
||||||
const upperEl = document.getElementById("key-upper");
|
|
||||||
const lowerEl = document.getElementById("key-lower");
|
|
||||||
const fbPriceEl = document.getElementById("key-fb-price");
|
|
||||||
const teEntryEl = document.getElementById("key-trigger-entry");
|
|
||||||
const teSlEl = document.getElementById("key-trigger-sl");
|
|
||||||
const teTpEl = document.getElementById("key-trigger-tp");
|
|
||||||
if(dirEl){
|
|
||||||
dirEl.style.display = showDir ? "" : "none";
|
|
||||||
dirEl.required = showDir;
|
|
||||||
if(!showDir) dirEl.value = "";
|
|
||||||
}
|
|
||||||
if(modeEl) modeEl.style.display = showAuto ? "" : "none";
|
|
||||||
if(manualTp){
|
|
||||||
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
|
||||||
manualTp.style.display = trend ? "" : "none";
|
|
||||||
manualTp.required = !!trend;
|
|
||||||
}
|
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
|
||||||
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
|
||||||
const hideBounds = showFb || showTe;
|
|
||||||
if(upperEl){
|
|
||||||
upperEl.style.display = hideBounds ? "none" : "";
|
|
||||||
upperEl.required = !hideBounds;
|
|
||||||
if(hideBounds) upperEl.value = "";
|
|
||||||
}
|
|
||||||
if(lowerEl){
|
|
||||||
lowerEl.style.display = hideBounds ? "none" : "";
|
|
||||||
lowerEl.required = !hideBounds;
|
|
||||||
if(hideBounds) lowerEl.value = "";
|
|
||||||
}
|
|
||||||
if(fbPriceEl){
|
|
||||||
fbPriceEl.style.display = showFb ? "" : "none";
|
|
||||||
fbPriceEl.required = showFb;
|
|
||||||
if(!showFb) fbPriceEl.value = "";
|
|
||||||
fbPriceEl.placeholder = (dirEl && dirEl.value === "short") ? "高点(阻力)" : ((dirEl && dirEl.value === "long") ? "低点(支撑)" : "做空填高点/做多填低点");
|
|
||||||
}
|
|
||||||
[teEntryEl, teSlEl, teTpEl].forEach((el)=>{
|
|
||||||
if(!el) return;
|
|
||||||
el.style.display = showTe ? "" : "none";
|
|
||||||
el.required = showTe;
|
|
||||||
if(!showTe) el.value = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const keyTypeSel = document.querySelector('#key-form [name="type"]');
|
|
||||||
const keyModeSel = document.getElementById("key-sl-tp-mode");
|
|
||||||
const keyDirSel = document.getElementById("key-direction");
|
|
||||||
if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
syncKeyMonitorFormFields();
|
|
||||||
if(window.TimeCloseUI){
|
if(window.TimeCloseUI){
|
||||||
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
|
||||||
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
|
||||||
if(keyForm){
|
|
||||||
keyForm.addEventListener("submit", (e)=>{
|
|
||||||
e.preventDefault();
|
|
||||||
if(window.FormSubmitGuard && FormSubmitGuard.isLocked(keyForm)) return;
|
|
||||||
const symbolEl = keyForm.querySelector('[name="symbol"]');
|
|
||||||
const symbol = (symbolEl ? symbolEl.value : "").trim();
|
|
||||||
if(!symbol){
|
|
||||||
alert("请先输入交易对");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const typeVal = (keyForm.querySelector('[name="type"]') || {}).value || "";
|
|
||||||
if(typeVal === "假突破"){
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.lock(keyForm, "校验排名中…");
|
|
||||||
fetch(`/api/symbol_liquidity_rank?symbol=${encodeURIComponent(symbol)}`)
|
|
||||||
.then(r=>r.json().then(d=>({status:r.status, data:d})))
|
|
||||||
.then(({status,data})=>{
|
|
||||||
if(status >= 400 || !data.ok){
|
|
||||||
alert((data && data.msg) || "日成交量排名读取失败");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rankMax = data.rank_max || 30;
|
|
||||||
if(!data.in_top30){
|
|
||||||
alert(`${data.symbol} 当前日成交量排名 ${data.rank}/${data.total},不在前${rankMax},已拦截。`);
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
})
|
|
||||||
.catch(()=>{
|
|
||||||
alert("日成交量排名检查失败,请稍后重试");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if(document.getElementById("journal-list")) loadJournals();
|
if(document.getElementById("journal-list")) loadJournals();
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ from key_monitor_lib import (
|
|||||||
KEY_DIRECTION_WATCH,
|
KEY_DIRECTION_WATCH,
|
||||||
KEY_MONITOR_ALERT_ONLY_TYPES,
|
KEY_MONITOR_ALERT_ONLY_TYPES,
|
||||||
KEY_MONITOR_AUTO_TYPES,
|
KEY_MONITOR_AUTO_TYPES,
|
||||||
|
KEY_MONITOR_RS_TYPE,
|
||||||
KEY_MONITOR_RS_TYPES,
|
KEY_MONITOR_RS_TYPES,
|
||||||
auto_amp_ok,
|
auto_amp_ok,
|
||||||
auto_confirm_ok,
|
auto_confirm_ok,
|
||||||
@@ -7684,6 +7685,7 @@ def add_key():
|
|||||||
direction_sel = (d.get("direction") or "").strip().lower()
|
direction_sel = (d.get("direction") or "").strip().lower()
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
direction_sel = KEY_DIRECTION_WATCH
|
direction_sel = KEY_DIRECTION_WATCH
|
||||||
|
mt = KEY_MONITOR_RS_TYPE
|
||||||
elif direction_sel not in ("long", "short"):
|
elif direction_sel not in ("long", "short"):
|
||||||
flash("箱体/收敛突破请选择做多或做空")
|
flash("箱体/收敛突破请选择做多或做空")
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
@@ -7723,7 +7725,7 @@ def add_key():
|
|||||||
conn = None
|
conn = None
|
||||||
flash(
|
flash(
|
||||||
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
||||||
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
|
"请平仓后再试,或使用「关键支撑阻力」(仅提醒)。"
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ex_sym_key = normalize_exchange_symbol(symbol)
|
ex_sym_key = normalize_exchange_symbol(symbol)
|
||||||
@@ -7925,7 +7927,7 @@ def add_key():
|
|||||||
extra += f"|{time_close_label(tc_h)}"
|
extra += f"|{time_close_label(tc_h)}"
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
flash(
|
flash(
|
||||||
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
f"添加成功({symbol} 日成交量排名 {rank}/{total})|关键支撑阻力:双向监控上/下沿,"
|
||||||
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -596,8 +596,7 @@
|
|||||||
<select name="type" required>
|
<select name="type" required>
|
||||||
<option value="箱体突破">箱体突破</option>
|
<option value="箱体突破">箱体突破</option>
|
||||||
<option value="收敛突破">收敛突破</option>
|
<option value="收敛突破">收敛突破</option>
|
||||||
<option value="关键阻力位">关键阻力位</option>
|
<option value="关键支撑阻力">关键支撑阻力</option>
|
||||||
<option value="关键支撑位">关键支撑位</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select name="direction" required>
|
<select name="direction" required>
|
||||||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||||||
@@ -1517,120 +1516,9 @@ if(journalForm){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncKeyMonitorFormFields(){
|
|
||||||
const typeEl = document.querySelector('#key-form [name="type"]');
|
|
||||||
const dirEl = document.getElementById("key-direction");
|
|
||||||
const modeEl = document.getElementById("key-sl-tp-mode");
|
|
||||||
const manualTp = document.getElementById("key-manual-tp");
|
|
||||||
const beWrap = document.getElementById("key-breakeven-wrap");
|
|
||||||
if(!typeEl) return;
|
|
||||||
const t = (typeEl.value || "").trim();
|
|
||||||
const autoTypes = new Set(["箱体突破","收敛突破"]);
|
|
||||||
const fibTypes = new Set(["斐波回调0.618","斐波回调0.786"]);
|
|
||||||
const fbTypes = new Set(["假突破"]);
|
|
||||||
const teTypes = new Set(["触价开仓"]);
|
|
||||||
const rsTypes = new Set(["关键阻力位","关键支撑位"]);
|
|
||||||
const showAuto = autoTypes.has(t);
|
|
||||||
const showFb = fbTypes.has(t);
|
|
||||||
const showTe = teTypes.has(t);
|
|
||||||
const showBe = showAuto || fibTypes.has(t) || showFb || showTe;
|
|
||||||
const showDir = !rsTypes.has(t);
|
|
||||||
const upperEl = document.getElementById("key-upper");
|
|
||||||
const lowerEl = document.getElementById("key-lower");
|
|
||||||
const fbPriceEl = document.getElementById("key-fb-price");
|
|
||||||
const teEntryEl = document.getElementById("key-trigger-entry");
|
|
||||||
const teSlEl = document.getElementById("key-trigger-sl");
|
|
||||||
const teTpEl = document.getElementById("key-trigger-tp");
|
|
||||||
if(dirEl){
|
|
||||||
dirEl.style.display = showDir ? "" : "none";
|
|
||||||
dirEl.required = showDir;
|
|
||||||
if(!showDir) dirEl.value = "";
|
|
||||||
}
|
|
||||||
if(modeEl) modeEl.style.display = showAuto ? "" : "none";
|
|
||||||
if(manualTp){
|
|
||||||
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
|
||||||
manualTp.style.display = trend ? "" : "none";
|
|
||||||
manualTp.required = !!trend;
|
|
||||||
}
|
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
|
||||||
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
|
||||||
const hideBounds = showFb || showTe;
|
|
||||||
if(upperEl){
|
|
||||||
upperEl.style.display = hideBounds ? "none" : "";
|
|
||||||
upperEl.required = !hideBounds;
|
|
||||||
if(hideBounds) upperEl.value = "";
|
|
||||||
}
|
|
||||||
if(lowerEl){
|
|
||||||
lowerEl.style.display = hideBounds ? "none" : "";
|
|
||||||
lowerEl.required = !hideBounds;
|
|
||||||
if(hideBounds) lowerEl.value = "";
|
|
||||||
}
|
|
||||||
if(fbPriceEl){
|
|
||||||
fbPriceEl.style.display = showFb ? "" : "none";
|
|
||||||
fbPriceEl.required = showFb;
|
|
||||||
if(!showFb) fbPriceEl.value = "";
|
|
||||||
fbPriceEl.placeholder = (dirEl && dirEl.value === "short") ? "高点(阻力)" : ((dirEl && dirEl.value === "long") ? "低点(支撑)" : "做空填高点/做多填低点");
|
|
||||||
}
|
|
||||||
[teEntryEl, teSlEl, teTpEl].forEach((el)=>{
|
|
||||||
if(!el) return;
|
|
||||||
el.style.display = showTe ? "" : "none";
|
|
||||||
el.required = showTe;
|
|
||||||
if(!showTe) el.value = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const keyTypeSel = document.querySelector('#key-form [name="type"]');
|
|
||||||
const keyModeSel = document.getElementById("key-sl-tp-mode");
|
|
||||||
const keyDirSel = document.getElementById("key-direction");
|
|
||||||
if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
syncKeyMonitorFormFields();
|
|
||||||
if(window.TimeCloseUI){
|
if(window.TimeCloseUI){
|
||||||
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
|
||||||
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
|
||||||
if(keyForm){
|
|
||||||
keyForm.addEventListener("submit", (e)=>{
|
|
||||||
e.preventDefault();
|
|
||||||
if(window.FormSubmitGuard && FormSubmitGuard.isLocked(keyForm)) return;
|
|
||||||
const symbolEl = keyForm.querySelector('[name="symbol"]');
|
|
||||||
const symbol = (symbolEl ? symbolEl.value : "").trim();
|
|
||||||
if(!symbol){
|
|
||||||
alert("请先输入交易对");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const typeVal = (keyForm.querySelector('[name="type"]') || {}).value || "";
|
|
||||||
if(typeVal === "假突破"){
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.lock(keyForm, "校验排名中…");
|
|
||||||
fetch(`/api/symbol_liquidity_rank?symbol=${encodeURIComponent(symbol)}`)
|
|
||||||
.then(r=>r.json().then(d=>({status:r.status, data:d})))
|
|
||||||
.then(({status,data})=>{
|
|
||||||
if(status >= 400 || !data.ok){
|
|
||||||
alert((data && data.msg) || "日成交量排名读取失败");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rankMax = data.rank_max || 30;
|
|
||||||
if(!data.in_top30){
|
|
||||||
alert(`${data.symbol} 当前日成交量排名 ${data.rank}/${data.total},不在前${rankMax},已拦截。`);
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
})
|
|
||||||
.catch(()=>{
|
|
||||||
alert("日成交量排名检查失败,请稍后重试");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if(document.getElementById("journal-list")) loadJournals();
|
if(document.getElementById("journal-list")) loadJournals();
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ from key_monitor_lib import (
|
|||||||
KEY_DIRECTION_WATCH,
|
KEY_DIRECTION_WATCH,
|
||||||
KEY_MONITOR_ALERT_ONLY_TYPES,
|
KEY_MONITOR_ALERT_ONLY_TYPES,
|
||||||
KEY_MONITOR_AUTO_TYPES,
|
KEY_MONITOR_AUTO_TYPES,
|
||||||
|
KEY_MONITOR_RS_TYPE,
|
||||||
KEY_MONITOR_RS_TYPES,
|
KEY_MONITOR_RS_TYPES,
|
||||||
auto_amp_ok,
|
auto_amp_ok,
|
||||||
auto_confirm_ok,
|
auto_confirm_ok,
|
||||||
@@ -7307,6 +7308,7 @@ def add_key():
|
|||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
direction_sel = KEY_DIRECTION_WATCH
|
direction_sel = KEY_DIRECTION_WATCH
|
||||||
|
mt = KEY_MONITOR_RS_TYPE
|
||||||
elif direction_sel not in ("long", "short"):
|
elif direction_sel not in ("long", "short"):
|
||||||
flash("箱体/收敛突破请选择做多或做空")
|
flash("箱体/收敛突破请选择做多或做空")
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
@@ -7343,7 +7345,7 @@ def add_key():
|
|||||||
conn.close()
|
conn.close()
|
||||||
flash(
|
flash(
|
||||||
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
||||||
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
|
"请平仓后再试,或使用「关键支撑阻力」(仅提醒)。"
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ex_sym_key = normalize_okx_symbol(symbol)
|
ex_sym_key = normalize_okx_symbol(symbol)
|
||||||
@@ -7525,7 +7527,7 @@ def add_key():
|
|||||||
pass
|
pass
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
flash(
|
flash(
|
||||||
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
f"添加成功({symbol} 日成交量排名 {rank}/{total})|关键支撑阻力:双向监控上/下沿,"
|
||||||
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
f"5m 收盘突破后微信提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分钟)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -625,8 +625,7 @@
|
|||||||
<select name="type" required>
|
<select name="type" required>
|
||||||
<option value="箱体突破">箱体突破</option>
|
<option value="箱体突破">箱体突破</option>
|
||||||
<option value="收敛突破">收敛突破</option>
|
<option value="收敛突破">收敛突破</option>
|
||||||
<option value="关键阻力位">关键阻力位</option>
|
<option value="关键支撑阻力">关键支撑阻力</option>
|
||||||
<option value="关键支撑位">关键支撑位</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select name="direction" required>
|
<select name="direction" required>
|
||||||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||||||
@@ -1546,121 +1545,9 @@ if(journalForm){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncKeyMonitorFormFields(){
|
|
||||||
const typeEl = document.querySelector('#key-form [name="type"]');
|
|
||||||
const dirEl = document.getElementById("key-direction");
|
|
||||||
const modeEl = document.getElementById("key-sl-tp-mode");
|
|
||||||
const manualTp = document.getElementById("key-manual-tp");
|
|
||||||
const beWrap = document.getElementById("key-breakeven-wrap");
|
|
||||||
if(!typeEl) return;
|
|
||||||
const t = (typeEl.value || "").trim();
|
|
||||||
const autoTypes = new Set(["箱体突破","收敛突破"]);
|
|
||||||
const fibTypes = new Set(["斐波回调0.618","斐波回调0.786"]);
|
|
||||||
const fbTypes = new Set(["假突破"]);
|
|
||||||
const teTypes = new Set(["触价开仓"]);
|
|
||||||
const rsTypes = new Set(["关键阻力位","关键支撑位"]);
|
|
||||||
const showAuto = autoTypes.has(t);
|
|
||||||
const showFb = fbTypes.has(t);
|
|
||||||
const showTe = teTypes.has(t);
|
|
||||||
const showBe = showAuto || fibTypes.has(t) || showFb || showTe;
|
|
||||||
const showDir = !rsTypes.has(t);
|
|
||||||
const upperEl = document.getElementById("key-upper");
|
|
||||||
const lowerEl = document.getElementById("key-lower");
|
|
||||||
const fbPriceEl = document.getElementById("key-fb-price");
|
|
||||||
const teEntryEl = document.getElementById("key-trigger-entry");
|
|
||||||
const teSlEl = document.getElementById("key-trigger-sl");
|
|
||||||
const teTpEl = document.getElementById("key-trigger-tp");
|
|
||||||
if(dirEl){
|
|
||||||
dirEl.style.display = showDir ? "" : "none";
|
|
||||||
dirEl.required = showDir;
|
|
||||||
if(!showDir) dirEl.value = "";
|
|
||||||
}
|
|
||||||
if(modeEl) modeEl.style.display = showAuto ? "" : "none";
|
|
||||||
if(manualTp){
|
|
||||||
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
|
||||||
manualTp.style.display = trend ? "" : "none";
|
|
||||||
manualTp.required = !!trend;
|
|
||||||
}
|
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
|
||||||
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
|
||||||
const hideBounds = showFb || showTe;
|
|
||||||
if(upperEl){
|
|
||||||
upperEl.style.display = hideBounds ? "none" : "";
|
|
||||||
upperEl.required = !hideBounds;
|
|
||||||
if(hideBounds) upperEl.value = "";
|
|
||||||
}
|
|
||||||
if(lowerEl){
|
|
||||||
lowerEl.style.display = hideBounds ? "none" : "";
|
|
||||||
lowerEl.required = !hideBounds;
|
|
||||||
if(hideBounds) lowerEl.value = "";
|
|
||||||
}
|
|
||||||
if(fbPriceEl){
|
|
||||||
fbPriceEl.style.display = showFb ? "" : "none";
|
|
||||||
fbPriceEl.required = showFb;
|
|
||||||
if(!showFb) fbPriceEl.value = "";
|
|
||||||
fbPriceEl.placeholder = (dirEl && dirEl.value === "short") ? "高点(阻力)" : ((dirEl && dirEl.value === "long") ? "低点(支撑)" : "做空填高点/做多填低点");
|
|
||||||
}
|
|
||||||
[teEntryEl, teSlEl, teTpEl].forEach((el)=>{
|
|
||||||
if(!el) return;
|
|
||||||
el.style.display = showTe ? "" : "none";
|
|
||||||
el.required = showTe;
|
|
||||||
if(!showTe) el.value = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const keyTypeSel = document.querySelector('#key-form [name="type"]');
|
|
||||||
const keyModeSel = document.getElementById("key-sl-tp-mode");
|
|
||||||
const keyDirSel = document.getElementById("key-direction");
|
|
||||||
if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
|
||||||
syncKeyMonitorFormFields();
|
|
||||||
if(window.TimeCloseUI){
|
if(window.TimeCloseUI){
|
||||||
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
|
||||||
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
|
||||||
if(keyForm){
|
|
||||||
keyForm.addEventListener("submit", (e)=>{
|
|
||||||
e.preventDefault();
|
|
||||||
if(window.FormSubmitGuard && FormSubmitGuard.isLocked(keyForm)) return;
|
|
||||||
const symbolEl = keyForm.querySelector('[name="symbol"]');
|
|
||||||
const symbol = (symbolEl ? symbolEl.value : "").trim();
|
|
||||||
if(!symbol){
|
|
||||||
alert("请先输入交易对");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const typeVal = (keyForm.querySelector('[name="type"]') || {}).value || "";
|
|
||||||
if(typeVal === "假突破"){
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.lock(keyForm, "校验排名中…");
|
|
||||||
fetch(`/api/symbol_liquidity_rank?symbol=${encodeURIComponent(symbol)}`)
|
|
||||||
.then(r=>r.json().then(d=>({status:r.status, data:d})))
|
|
||||||
.then(({status,data})=>{
|
|
||||||
if(status >= 400 || !data.ok){
|
|
||||||
alert((data && data.msg) || "日成交量排名读取失败");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rankMax = data.rank_max || 30;
|
|
||||||
const inTop = data.in_top != null ? data.in_top : data.in_top30;
|
|
||||||
if(data.rank == null || !inTop){
|
|
||||||
alert(`${data.symbol} 当前24h成交额排名 ${data.rank == null ? "—" : data.rank}/${data.total},不在前${rankMax},已拦截。`);
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
|
||||||
else keyForm.submit();
|
|
||||||
})
|
|
||||||
.catch(()=>{
|
|
||||||
alert("日成交量排名检查失败,请稍后重试");
|
|
||||||
if(window.FormSubmitGuard) FormSubmitGuard.unlock(keyForm);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
// 复盘/AI列表:初次进入页面后再异步刷新一次,避免浏览器 bfcache/重定向后仍显示旧缓存
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if(document.getElementById("journal-list")) loadJournals();
|
if(document.getElementById("journal-list")) loadJournals();
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ def install_instance_theme_static(app) -> None:
|
|||||||
"instance_ui.js": "application/javascript; charset=utf-8",
|
"instance_ui.js": "application/javascript; charset=utf-8",
|
||||||
"ai_review_render.js": "application/javascript; charset=utf-8",
|
"ai_review_render.js": "application/javascript; charset=utf-8",
|
||||||
"form_submit_guard.js": "application/javascript; charset=utf-8",
|
"form_submit_guard.js": "application/javascript; charset=utf-8",
|
||||||
|
"key_monitor_form.js": "application/javascript; charset=utf-8",
|
||||||
"time_close_ui.js": "application/javascript; charset=utf-8",
|
"time_close_ui.js": "application/javascript; charset=utf-8",
|
||||||
"focus_chart_page.js": "application/javascript; charset=utf-8",
|
"focus_chart_page.js": "application/javascript; charset=utf-8",
|
||||||
"focus_chart_page.css": "text/css; charset=utf-8",
|
"focus_chart_page.css": "text/css; charset=utf-8",
|
||||||
|
|||||||
+21
-2
@@ -7,11 +7,30 @@ from datetime import datetime
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
KEY_MONITOR_AUTO_TYPES = frozenset({"箱体突破", "收敛突破"})
|
KEY_MONITOR_AUTO_TYPES = frozenset({"箱体突破", "收敛突破"})
|
||||||
KEY_MONITOR_RS_TYPES = frozenset({"关键阻力位", "关键支撑位"})
|
KEY_MONITOR_RS_TYPE = "关键支撑阻力"
|
||||||
KEY_MONITOR_ALERT_ONLY_TYPES = KEY_MONITOR_RS_TYPES
|
KEY_MONITOR_RS_LEGACY_TYPES = frozenset({"关键阻力位", "关键支撑位"})
|
||||||
|
KEY_MONITOR_RS_TYPES = frozenset({KEY_MONITOR_RS_TYPE}) | KEY_MONITOR_RS_LEGACY_TYPES
|
||||||
|
KEY_MONITOR_ALERT_ONLY_TYPES = frozenset({KEY_MONITOR_RS_TYPE}) | KEY_MONITOR_RS_LEGACY_TYPES
|
||||||
KEY_DIRECTION_WATCH = "watch"
|
KEY_DIRECTION_WATCH = "watch"
|
||||||
|
|
||||||
|
|
||||||
|
def is_rs_key_monitor_type(monitor_type: str) -> bool:
|
||||||
|
return (monitor_type or "").strip() in KEY_MONITOR_RS_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def rs_monitor_type_label(monitor_type: str) -> str:
|
||||||
|
"""展示用:旧库里的阻力/支撑合并为「关键支撑阻力」。"""
|
||||||
|
if is_rs_key_monitor_type(monitor_type):
|
||||||
|
return KEY_MONITOR_RS_TYPE
|
||||||
|
return (monitor_type or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def rs_monitor_type_for_storage(monitor_type: str) -> str:
|
||||||
|
if is_rs_key_monitor_type(monitor_type):
|
||||||
|
return KEY_MONITOR_RS_TYPE
|
||||||
|
return (monitor_type or "").strip()
|
||||||
|
|
||||||
|
|
||||||
def calc_breakout_breach_pct(direction: str, close: float, upper: float, lower: float) -> float:
|
def calc_breakout_breach_pct(direction: str, close: float, upper: float, lower: float) -> float:
|
||||||
"""突破 K 收盘相对关键位的越过幅度(%)。未越过对应边界时返回 0。"""
|
"""突破 K 收盘相对关键位的越过幅度(%)。未越过对应边界时返回 0。"""
|
||||||
direction = (direction or "long").strip().lower()
|
direction = (direction or "long").strip().lower()
|
||||||
|
|||||||
@@ -2940,6 +2940,7 @@
|
|||||||
"关键位收敛结构",
|
"关键位收敛结构",
|
||||||
]);
|
]);
|
||||||
const KEY_BUCKET_WATCH_TYPES = new Set([
|
const KEY_BUCKET_WATCH_TYPES = new Set([
|
||||||
|
"关键支撑阻力",
|
||||||
"关键阻力位",
|
"关键阻力位",
|
||||||
"关键支撑位",
|
"关键支撑位",
|
||||||
"关键位监控",
|
"关键位监控",
|
||||||
@@ -3065,7 +3066,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="fs-head-actions">
|
<div class="fs-head-actions">
|
||||||
<button type="button" class="ghost btn-expand-back">返回监控</button>
|
<button type="button" class="ghost btn-expand-back">返回监控</button>
|
||||||
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/">打开实例</a>` : ""}
|
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/key_monitor">关键位监控</a>` : ""}
|
||||||
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/strategy">策略交易</a>` : ""}
|
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/strategy">策略交易</a>` : ""}
|
||||||
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -3371,6 +3372,9 @@
|
|||||||
const openFlask = flaskOpen
|
const openFlask = flaskOpen
|
||||||
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/">实例</a>`
|
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/">实例</a>`
|
||||||
: "";
|
: "";
|
||||||
|
const openKey = flaskOpen
|
||||||
|
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/key_monitor">关键位</a>`
|
||||||
|
: "";
|
||||||
const openReview = flaskOpen
|
const openReview = flaskOpen
|
||||||
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/records">复盘</a>`
|
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/records">复盘</a>`
|
||||||
: "";
|
: "";
|
||||||
@@ -3385,6 +3389,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
${openFlask}
|
${openFlask}
|
||||||
|
${openKey}
|
||||||
${openReview}
|
${openReview}
|
||||||
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* 关键位监控添加表单:类型切换显隐、成交量排名校验(四所实例共用)。
|
||||||
|
*/
|
||||||
|
(function (global) {
|
||||||
|
const RS_TYPES = new Set([
|
||||||
|
"关键支撑阻力",
|
||||||
|
"关键阻力位",
|
||||||
|
"关键支撑位",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function syncKeyMonitorFormFields() {
|
||||||
|
const typeEl = document.querySelector('#key-form [name="type"]');
|
||||||
|
const dirEl = document.getElementById("key-direction");
|
||||||
|
const modeEl = document.getElementById("key-sl-tp-mode");
|
||||||
|
const manualTp = document.getElementById("key-manual-tp");
|
||||||
|
const beWrap = document.getElementById("key-breakeven-wrap");
|
||||||
|
if (!typeEl) return;
|
||||||
|
const t = (typeEl.value || "").trim();
|
||||||
|
const autoTypes = new Set(["箱体突破", "收敛突破"]);
|
||||||
|
const fibTypes = new Set(["斐波回调0.618", "斐波回调0.786"]);
|
||||||
|
const fbTypes = new Set(["假突破"]);
|
||||||
|
const teTypes = new Set(["触价开仓"]);
|
||||||
|
const showAuto = autoTypes.has(t);
|
||||||
|
const showFb = fbTypes.has(t);
|
||||||
|
const showTe = teTypes.has(t);
|
||||||
|
const showBe = showAuto || fibTypes.has(t) || showFb || showTe;
|
||||||
|
const showDir = !RS_TYPES.has(t);
|
||||||
|
const upperEl = document.getElementById("key-upper");
|
||||||
|
const lowerEl = document.getElementById("key-lower");
|
||||||
|
const fbPriceEl = document.getElementById("key-fb-price");
|
||||||
|
const teEntryEl = document.getElementById("key-trigger-entry");
|
||||||
|
const teSlEl = document.getElementById("key-trigger-sl");
|
||||||
|
const teTpEl = document.getElementById("key-trigger-tp");
|
||||||
|
if (dirEl) {
|
||||||
|
dirEl.style.display = showDir ? "" : "none";
|
||||||
|
dirEl.required = showDir;
|
||||||
|
if (!showDir) dirEl.value = "";
|
||||||
|
}
|
||||||
|
if (modeEl) modeEl.style.display = showAuto ? "" : "none";
|
||||||
|
if (manualTp) {
|
||||||
|
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
||||||
|
manualTp.style.display = trend ? "" : "none";
|
||||||
|
manualTp.required = !!trend;
|
||||||
|
}
|
||||||
|
if (beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
||||||
|
if (global.TimeCloseUI) global.TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
||||||
|
const hideBounds = showFb || showTe;
|
||||||
|
if (upperEl) {
|
||||||
|
upperEl.style.display = hideBounds ? "none" : "";
|
||||||
|
upperEl.required = !hideBounds;
|
||||||
|
if (hideBounds) upperEl.value = "";
|
||||||
|
}
|
||||||
|
if (lowerEl) {
|
||||||
|
lowerEl.style.display = hideBounds ? "none" : "";
|
||||||
|
lowerEl.required = !hideBounds;
|
||||||
|
if (hideBounds) lowerEl.value = "";
|
||||||
|
}
|
||||||
|
if (fbPriceEl) {
|
||||||
|
fbPriceEl.style.display = showFb ? "" : "none";
|
||||||
|
fbPriceEl.required = showFb;
|
||||||
|
if (!showFb) fbPriceEl.value = "";
|
||||||
|
fbPriceEl.placeholder =
|
||||||
|
dirEl && dirEl.value === "short"
|
||||||
|
? "高点(阻力)"
|
||||||
|
: dirEl && dirEl.value === "long"
|
||||||
|
? "低点(支撑)"
|
||||||
|
: "做空填高点/做多填低点";
|
||||||
|
}
|
||||||
|
[teEntryEl, teSlEl, teTpEl].forEach((el) => {
|
||||||
|
if (!el) return;
|
||||||
|
el.style.display = showTe ? "" : "none";
|
||||||
|
el.required = showTe;
|
||||||
|
if (!showTe) el.value = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindKeyMonitorForm() {
|
||||||
|
const keyForm = document.getElementById("key-form");
|
||||||
|
const keyTypeSel = document.querySelector('#key-form [name="type"]');
|
||||||
|
const keyModeSel = document.getElementById("key-sl-tp-mode");
|
||||||
|
const keyDirSel = document.getElementById("key-direction");
|
||||||
|
if (keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
|
if (keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
|
if (keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
|
syncKeyMonitorFormFields();
|
||||||
|
if (global.TimeCloseUI) {
|
||||||
|
global.TimeCloseUI.bindTimeCloseForm(
|
||||||
|
"key-time-close-cb",
|
||||||
|
"key-time-close-hours",
|
||||||
|
"key-time-close-wrap"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!keyForm || keyForm.dataset.keyFormBound === "1") return;
|
||||||
|
keyForm.dataset.keyFormBound = "1";
|
||||||
|
keyForm.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (global.FormSubmitGuard && global.FormSubmitGuard.isLocked(keyForm)) return;
|
||||||
|
const symbolEl = keyForm.querySelector('[name="symbol"]');
|
||||||
|
const symbol = (symbolEl ? symbolEl.value : "").trim();
|
||||||
|
if (!symbol) {
|
||||||
|
alert("请先输入交易对");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const typeVal = (keyForm.querySelector('[name="type"]') || {}).value || "";
|
||||||
|
if (typeVal === "假突破") {
|
||||||
|
if (global.FormSubmitGuard) global.FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
||||||
|
else keyForm.submit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (global.FormSubmitGuard) global.FormSubmitGuard.lock(keyForm, "校验排名中…");
|
||||||
|
fetch(`/api/symbol_liquidity_rank?symbol=${encodeURIComponent(symbol)}`)
|
||||||
|
.then((r) => r.json().then((d) => ({ status: r.status, data: d })))
|
||||||
|
.then(({ status, data }) => {
|
||||||
|
if (status >= 400 || !data.ok) {
|
||||||
|
alert((data && data.msg) || "日成交量排名读取失败");
|
||||||
|
if (global.FormSubmitGuard) global.FormSubmitGuard.unlock(keyForm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rankMax = data.rank_max || 30;
|
||||||
|
const inTop = data.in_top != null ? data.in_top : data.in_top30;
|
||||||
|
if (data.rank == null || !inTop) {
|
||||||
|
alert(
|
||||||
|
`${data.symbol} 当前日成交量排名 ${data.rank == null ? "—" : data.rank}/${data.total},不在前${rankMax},已拦截。`
|
||||||
|
);
|
||||||
|
if (global.FormSubmitGuard) global.FormSubmitGuard.unlock(keyForm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (global.FormSubmitGuard) global.FormSubmitGuard.nativeSubmitOnce(keyForm, "提交中…");
|
||||||
|
else keyForm.submit();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert("日成交量排名检查失败,请稍后重试");
|
||||||
|
if (global.FormSubmitGuard) global.FormSubmitGuard.unlock(keyForm);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
global.KeyMonitorForm = {
|
||||||
|
syncFields: syncKeyMonitorFormFields,
|
||||||
|
init: bindKeyMonitorForm,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", bindKeyMonitorForm);
|
||||||
|
} else {
|
||||||
|
bindKeyMonitorForm();
|
||||||
|
}
|
||||||
|
})(typeof window !== "undefined" ? window : globalThis);
|
||||||
@@ -77,6 +77,10 @@
|
|||||||
.key-rule-foot code{font-size:.54rem;color:#8fc8ff}
|
.key-rule-foot code{font-size:.54rem;color:#8fc8ff}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
{% macro key_monitor_type_label(k) -%}
|
||||||
|
{%- if k.monitor_type in ['关键阻力位','关键支撑位','关键支撑阻力'] -%}关键支撑阻力{%- else -%}{{ k.monitor_type }}{%- endif -%}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro key_direction_label(k) -%}
|
{% macro key_direction_label(k) -%}
|
||||||
{% if k.direction == 'watch' %}双向{% elif k.direction == 'long' %}做多{% else %}做空{% endif %}
|
{% if k.direction == 'watch' %}双向{% elif k.direction == 'long' %}做多{% else %}做空{% endif %}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
@@ -146,8 +150,7 @@
|
|||||||
<option value="假突破">假突破(BTC/ETH)</option>
|
<option value="假突破">假突破(BTC/ETH)</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<option value="触价开仓">触价开仓</option>
|
<option value="触价开仓">触价开仓</option>
|
||||||
<option value="关键阻力位">关键阻力位</option>
|
<option value="关键支撑阻力">关键支撑阻力</option>
|
||||||
<option value="关键支撑位">关键支撑位</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select name="direction" id="key-direction" required>
|
<select name="direction" id="key-direction" required>
|
||||||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||||||
@@ -200,7 +203,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<span class="pos-side-badge {{ 'pos-side-long' if k.direction == 'long' else 'pos-side-short' }}">{{ key_direction_label(k) }}</span>
|
<span class="pos-side-badge {{ 'pos-side-long' if k.direction == 'long' else 'pos-side-short' }}">{{ key_direction_label(k) }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="badge direction">{{ k.monitor_type }}</span>
|
<span class="badge direction">{{ key_monitor_type_label(k) }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="key-row-summary-live" id="key-summary-live-{{ k.id }}">现价 — · 门控 —</span>
|
<span class="key-row-summary-live" id="key-summary-live-{{ k.id }}">现价 — · 门控 —</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -246,7 +249,7 @@
|
|||||||
<span class="key-row-summary-title">
|
<span class="key-row-summary-title">
|
||||||
<strong>{{ h.symbol }}</strong>
|
<strong>{{ h.symbol }}</strong>
|
||||||
<span class="pos-side-badge {{ 'pos-side-long' if h.direction == 'long' else 'pos-side-short' }}">{{ key_direction_label(h) }}</span>
|
<span class="pos-side-badge {{ 'pos-side-long' if h.direction == 'long' else 'pos-side-short' }}">{{ key_direction_label(h) }}</span>
|
||||||
<span class="badge direction">{{ h.monitor_type }}</span>
|
<span class="badge direction">{{ key_monitor_type_label(h) }}</span>
|
||||||
<span class="key-history-outcome-badge">{{ key_history_outcome_label(h) }}</span>
|
<span class="key-history-outcome-badge">{{ key_history_outcome_label(h) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -257,7 +260,7 @@
|
|||||||
<div class="key-row-collapse-body">
|
<div class="key-row-collapse-body">
|
||||||
<div class="key-row-summary-line key-history-brief">{{ key_history_brief(h) }}</div>
|
<div class="key-row-summary-line key-history-brief">{{ key_history_brief(h) }}</div>
|
||||||
<div class="pos-meta">
|
<div class="pos-meta">
|
||||||
<span class="pos-meta-item">类型: {{ h.monitor_type }}</span>
|
<span class="pos-meta-item">类型: {{ key_monitor_type_label(h) }}</span>
|
||||||
<span class="pos-meta-item">结案: {{ key_history_outcome_label(h) }}{% if h.close_reason %} ({{ h.close_reason }}){% endif %}</span>
|
<span class="pos-meta-item">结案: {{ key_history_outcome_label(h) }}{% if h.close_reason %} ({{ h.close_reason }}){% endif %}</span>
|
||||||
<span class="pos-meta-item">时间: {{ h.closed_at or '—' }}</span>
|
<span class="pos-meta-item">时间: {{ h.closed_at or '—' }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,3 +321,4 @@ document.querySelectorAll(".key-row-collapse").forEach((row)=>{
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/static/key_monitor_form.js?v=1"></script>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<td class="key-rule-cell">占当日开仓意图<br>全仓模式可用</td>
|
<td class="key-rule-cell">占当日开仓意图<br>全仓模式可用</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="key-rule-type">阻力 / 支撑</td>
|
<td class="key-rule-type">关键支撑阻力</td>
|
||||||
<td class="key-rule-cell">双向;填上/下沿</td>
|
<td class="key-rule-cell">双向;填上/下沿</td>
|
||||||
<td class="key-rule-cell">{{ r.tf }} 收盘破上沿或下沿<br>上沿优先</td>
|
<td class="key-rule-cell">{{ r.tf }} 收盘破上沿或下沿<br>上沿优先</td>
|
||||||
<td class="key-rule-cell">无(仅提醒)</td>
|
<td class="key-rule-cell">无(仅提醒)</td>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from key_monitor_lib import (
|
||||||
|
KEY_MONITOR_RS_TYPE,
|
||||||
|
is_rs_key_monitor_type,
|
||||||
|
rs_monitor_type_for_storage,
|
||||||
|
rs_monitor_type_label,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KeyMonitorRsTypeTests(unittest.TestCase):
|
||||||
|
def test_legacy_types_still_recognized(self):
|
||||||
|
self.assertTrue(is_rs_key_monitor_type("关键阻力位"))
|
||||||
|
self.assertTrue(is_rs_key_monitor_type("关键支撑位"))
|
||||||
|
|
||||||
|
def test_storage_normalizes_to_unified_type(self):
|
||||||
|
self.assertEqual(rs_monitor_type_for_storage("关键阻力位"), KEY_MONITOR_RS_TYPE)
|
||||||
|
self.assertEqual(rs_monitor_type_for_storage("关键支撑位"), KEY_MONITOR_RS_TYPE)
|
||||||
|
self.assertEqual(rs_monitor_type_for_storage(KEY_MONITOR_RS_TYPE), KEY_MONITOR_RS_TYPE)
|
||||||
|
|
||||||
|
def test_label_merges_legacy_display(self):
|
||||||
|
self.assertEqual(rs_monitor_type_label("关键阻力位"), KEY_MONITOR_RS_TYPE)
|
||||||
|
self.assertEqual(rs_monitor_type_label("箱体突破"), "箱体突破")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user