Improve key monitor form with bar period, box direction, and labeled fields.
Match order-monitor layout; persist bar_period and enforce upper-direction filter for box breakouts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -305,6 +305,7 @@ def init_db():
|
|||||||
"ALTER TABLE key_monitors ADD COLUMN alert_break_side TEXT",
|
"ALTER TABLE key_monitors ADD COLUMN alert_break_side TEXT",
|
||||||
"ALTER TABLE key_monitors ADD COLUMN breakout_bar_time TEXT",
|
"ALTER TABLE key_monitors ADD COLUMN breakout_bar_time TEXT",
|
||||||
"ALTER TABLE key_monitors ADD COLUMN alert_close_price REAL",
|
"ALTER TABLE key_monitors ADD COLUMN alert_close_price REAL",
|
||||||
|
"ALTER TABLE key_monitors ADD COLUMN bar_period TEXT DEFAULT '5m'",
|
||||||
"ALTER TABLE review_records ADD COLUMN direction TEXT",
|
"ALTER TABLE review_records ADD COLUMN direction TEXT",
|
||||||
"ALTER TABLE review_records ADD COLUMN entry_price REAL",
|
"ALTER TABLE review_records ADD COLUMN entry_price REAL",
|
||||||
"ALTER TABLE review_records ADD COLUMN stop_loss REAL",
|
"ALTER TABLE review_records ADD COLUMN stop_loss REAL",
|
||||||
@@ -1057,6 +1058,8 @@ def ai_messages_page():
|
|||||||
@app.route("/keys")
|
@app.route("/keys")
|
||||||
@login_required
|
@login_required
|
||||||
def keys():
|
def keys():
|
||||||
|
from key_monitor_lib import key_monitor_periods
|
||||||
|
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
key_list = conn.execute(
|
key_list = conn.execute(
|
||||||
"SELECT * FROM key_monitors WHERE status='active' OR status IS NULL ORDER BY id DESC"
|
"SELECT * FROM key_monitors WHERE status='active' OR status IS NULL ORDER BY id DESC"
|
||||||
@@ -1065,7 +1068,12 @@ def keys():
|
|||||||
"SELECT * FROM key_monitors WHERE status='archived' ORDER BY archived_at DESC LIMIT 100"
|
"SELECT * FROM key_monitors WHERE status='archived' ORDER BY archived_at DESC LIMIT 100"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return render_template("keys.html", keys=key_list, history=history)
|
return render_template(
|
||||||
|
"keys.html",
|
||||||
|
keys=key_list,
|
||||||
|
history=history,
|
||||||
|
key_periods=key_monitor_periods(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1103,21 +1111,26 @@ def add_key():
|
|||||||
if trailing_be and risk_reward < 3:
|
if trailing_be and risk_reward < 3:
|
||||||
risk_reward = 3.0
|
risk_reward = 3.0
|
||||||
|
|
||||||
|
from key_monitor_lib import normalize_bar_period
|
||||||
|
|
||||||
|
bar_period = normalize_bar_period(d.get("bar_period") or "5m")
|
||||||
direction = (d.get("direction") or "").strip().lower()
|
direction = (d.get("direction") or "").strip().lower()
|
||||||
if monitor_type in ("箱体突破", "收敛突破"):
|
if monitor_type == "箱体突破":
|
||||||
direction = direction or "long"
|
if direction not in ("long", "short"):
|
||||||
|
flash("箱体突破须选择上方向(做多/做空)")
|
||||||
|
return redirect(url_for("keys"))
|
||||||
else:
|
else:
|
||||||
direction = direction or "long"
|
direction = ""
|
||||||
|
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO key_monitors
|
"""INSERT INTO key_monitors
|
||||||
(symbol, symbol_name, market_code, sina_code, monitor_type, direction,
|
(symbol, symbol_name, market_code, sina_code, monitor_type, direction,
|
||||||
upper, lower, trade_mode, risk_reward, trailing_be)
|
upper, lower, trade_mode, risk_reward, trailing_be, bar_period)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
symbol, symbol_name, market_code, sina_code, monitor_type, direction,
|
symbol, symbol_name, market_code, sina_code, monitor_type, direction,
|
||||||
upper, lower, trade_mode, risk_reward, trailing_be,
|
upper, lower, trade_mode, risk_reward, trailing_be, bar_period,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -2933,6 +2933,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
def _execute_key_breakout(conn, row, bar, break_side):
|
def _execute_key_breakout(conn, row, bar, break_side):
|
||||||
"""关键位箱体/收敛:5m 收盘突破后自动市价开仓。"""
|
"""关键位箱体/收敛:5m 收盘突破后自动市价开仓。"""
|
||||||
from key_monitor_lib import (
|
from key_monitor_lib import (
|
||||||
|
TYPE_BOX,
|
||||||
calc_breakout_sl_tp,
|
calc_breakout_sl_tp,
|
||||||
format_auto_breakout_msg,
|
format_auto_breakout_msg,
|
||||||
normalize_monitor_type,
|
normalize_monitor_type,
|
||||||
@@ -2966,6 +2967,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
detail=detail,
|
detail=detail,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if monitor_type == TYPE_BOX:
|
||||||
|
cfg_dir = (row.get("direction") or "").strip().lower()
|
||||||
|
if cfg_dir in ("long", "short") and direction != cfg_dir:
|
||||||
|
dir_cn = "做多" if cfg_dir == "long" else "做空"
|
||||||
|
_notify(False, f"突破方向与上方向({dir_cn})不一致", entry=0, sl=0, tp=0, lots=0)
|
||||||
|
return False, "突破方向与上方向不一致"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
init_strategy_tables(conn)
|
init_strategy_tables(conn)
|
||||||
mode = get_trading_mode(get_setting)
|
mode = get_trading_mode(get_setting)
|
||||||
|
|||||||
+49
-10
@@ -27,8 +27,38 @@ ZONE_TYPES = (TYPE_ZONE, "关键阻力位", "关键支撑位")
|
|||||||
ALERT_MAX_PUSH = 3
|
ALERT_MAX_PUSH = 3
|
||||||
ALERT_INTERVAL_SEC = 300
|
ALERT_INTERVAL_SEC = 300
|
||||||
SL_TICK_BUFFER = 2
|
SL_TICK_BUFFER = 2
|
||||||
BAR_PERIOD = "5m"
|
DEFAULT_BAR_PERIOD = "5m"
|
||||||
BAR_MINUTES = 5
|
|
||||||
|
PERIOD_MINUTES_MAP = {
|
||||||
|
"1m": 1, "2m": 2, "3m": 3, "5m": 5, "15m": 15, "30m": 30,
|
||||||
|
"1h": 60, "2h": 120, "4h": 240, "d": 1440, "1d": 1440,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def key_monitor_periods() -> list[dict[str, str]]:
|
||||||
|
"""关键位监控可选 K 线周期(触发用)。"""
|
||||||
|
from kline_chart import MARKET_PERIODS
|
||||||
|
|
||||||
|
allowed = frozenset({"5m", "15m", "30m", "1h", "2h", "4h", "d"})
|
||||||
|
return [p for p in MARKET_PERIODS if p["key"] in allowed]
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_bar_period(raw: str) -> str:
|
||||||
|
valid = {p["key"] for p in key_monitor_periods()}
|
||||||
|
k = (raw or DEFAULT_BAR_PERIOD).strip()
|
||||||
|
return k if k in valid else DEFAULT_BAR_PERIOD
|
||||||
|
|
||||||
|
|
||||||
|
def bar_period_label(key: str) -> str:
|
||||||
|
k = normalize_bar_period(key)
|
||||||
|
for p in key_monitor_periods():
|
||||||
|
if p["key"] == k:
|
||||||
|
return p["label"]
|
||||||
|
return k
|
||||||
|
|
||||||
|
|
||||||
|
def bar_period_minutes(period: str) -> int:
|
||||||
|
return PERIOD_MINUTES_MAP.get(normalize_bar_period(period), 5)
|
||||||
|
|
||||||
|
|
||||||
def normalize_monitor_type(raw: str) -> str:
|
def normalize_monitor_type(raw: str) -> str:
|
||||||
@@ -95,14 +125,19 @@ def _parse_bar_time(raw: str) -> Optional[datetime]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def last_closed_bar(bars: list[dict], now: Optional[datetime] = None) -> Optional[dict]:
|
def last_closed_bar(
|
||||||
|
bars: list[dict],
|
||||||
|
period_minutes: int = 5,
|
||||||
|
now: Optional[datetime] = None,
|
||||||
|
) -> Optional[dict]:
|
||||||
"""取最近一根已收盘 K 线。"""
|
"""取最近一根已收盘 K 线。"""
|
||||||
dnow = now or datetime.now(TZ)
|
dnow = now or datetime.now(TZ)
|
||||||
|
mins = max(1, int(period_minutes or 5))
|
||||||
for bar in reversed(bars or []):
|
for bar in reversed(bars or []):
|
||||||
dt = _parse_bar_time(str(bar.get("time") or ""))
|
dt = _parse_bar_time(str(bar.get("time") or ""))
|
||||||
if not dt:
|
if not dt:
|
||||||
continue
|
continue
|
||||||
bar_end = dt + timedelta(minutes=BAR_MINUTES)
|
bar_end = dt + timedelta(minutes=mins)
|
||||||
if dnow >= bar_end:
|
if dnow >= bar_end:
|
||||||
return bar
|
return bar
|
||||||
return None
|
return None
|
||||||
@@ -116,24 +151,26 @@ def detect_break_side(close: float, upper: float, lower: float) -> Optional[str]
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def fetch_closed_5m_bar(
|
def fetch_closed_bar(
|
||||||
sym: str,
|
sym: str,
|
||||||
|
period: str,
|
||||||
*,
|
*,
|
||||||
db_path: str,
|
db_path: str,
|
||||||
trading_mode: str,
|
trading_mode: str,
|
||||||
) -> Optional[dict]:
|
) -> Optional[dict]:
|
||||||
|
p = normalize_bar_period(period)
|
||||||
try:
|
try:
|
||||||
data = fetch_market_klines(
|
data = fetch_market_klines(
|
||||||
sym,
|
sym,
|
||||||
BAR_PERIOD,
|
p,
|
||||||
db_path=db_path,
|
db_path=db_path,
|
||||||
trading_mode=trading_mode,
|
trading_mode=trading_mode,
|
||||||
prefer_ctp=True,
|
prefer_ctp=True,
|
||||||
)
|
)
|
||||||
bars = data.get("bars") or []
|
bars = data.get("bars") or []
|
||||||
return last_closed_bar(bars)
|
return last_closed_bar(bars, bar_period_minutes(p))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("key monitor kline %s: %s", sym, exc)
|
logger.debug("key monitor kline %s %s: %s", sym, p, exc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -201,9 +238,10 @@ def format_auto_breakout_msg(
|
|||||||
break_label, _ = break_direction_label(break_side)
|
break_label, _ = break_direction_label(break_side)
|
||||||
dir_cn = "做多" if direction == "long" else "做空"
|
dir_cn = "做多" if direction == "long" else "做空"
|
||||||
rr = float(row.get("risk_reward") or 2)
|
rr = float(row.get("risk_reward") or 2)
|
||||||
|
period_label = bar_period_label(row.get("bar_period") or DEFAULT_BAR_PERIOD)
|
||||||
lines = [
|
lines = [
|
||||||
f"{'✅' if ok else '❌'} {name} {typ}自动单",
|
f"{'✅' if ok else '❌'} {name} {typ}自动单",
|
||||||
f"⏱ 5m 收盘:{bar_time}",
|
f"⏱ {period_label} 收盘:{bar_time}",
|
||||||
f"🎯 {break_label} · {trade_mode} · {dir_cn}",
|
f"🎯 {break_label} · {trade_mode} · {dir_cn}",
|
||||||
f"💹 入场:{entry:g} 止损:{sl:g} 止盈:{tp:g}(盈亏比 {rr:g})",
|
f"💹 入场:{entry:g} 止损:{sl:g} 止盈:{tp:g}(盈亏比 {rr:g})",
|
||||||
f"📦 手数:{lots}",
|
f"📦 手数:{lots}",
|
||||||
@@ -333,7 +371,8 @@ def run_key_monitor_check(
|
|||||||
archive_monitor(conn, pid)
|
archive_monitor(conn, pid)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bar = fetch_closed_5m_bar(sym, db_path=db_path, trading_mode=mode)
|
bar_period = normalize_bar_period(row.get("bar_period") or DEFAULT_BAR_PERIOD)
|
||||||
|
bar = fetch_closed_bar(sym, bar_period, db_path=db_path, trading_mode=mode)
|
||||||
if not bar:
|
if not bar:
|
||||||
continue
|
continue
|
||||||
bar_time = str(bar.get("time") or "")[:19]
|
bar_time = str(bar.get("time") or "")[:19]
|
||||||
|
|||||||
+17
-8
@@ -3,11 +3,20 @@
|
|||||||
.key-rules-body{padding:.35rem 0 .15rem}
|
.key-rules-body{padding:.35rem 0 .15rem}
|
||||||
.key-rules-body ul{margin:.25rem 0 .5rem 1.1rem;padding:0}
|
.key-rules-body ul{margin:.25rem 0 .5rem 1.1rem;padding:0}
|
||||||
.key-rules-body li{margin:.15rem 0}
|
.key-rules-body li{margin:.15rem 0}
|
||||||
.key-check{display:inline-flex;align-items:center;gap:.35rem;font-size:.82rem;flex:1;min-width:0;margin:0}
|
.key-form-rows{display:flex;flex-direction:column;gap:.75rem;margin-bottom:.85rem}
|
||||||
.key-check-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
.key-form-line{display:grid;gap:.65rem;align-items:end}
|
||||||
.line-key-actions{display:flex;align-items:center;justify-content:space-between;gap:.75rem;flex-wrap:nowrap}
|
.key-form-line.line-3{grid-template-columns:1.4fr .85fr .85fr}
|
||||||
.line-key-actions.is-hidden{display:none!important}
|
.key-form-line.line-2{grid-template-columns:1fr 1fr}
|
||||||
.line-key-actions .key-submit-btn{flex-shrink:0;min-width:5.5rem;padding:.55rem 1.1rem}
|
.key-field label{display:block;font-size:.72rem;margin-bottom:.28rem;color:var(--text-label)}
|
||||||
.line-key-actions.key-actions-zone{justify-content:flex-end}
|
.key-field select,.key-field input{width:100%;box-sizing:border-box}
|
||||||
.line-key-actions.key-actions-zone .key-check{display:none}
|
.key-action-row{display:flex;flex-direction:column;gap:.35rem;margin-top:.15rem}
|
||||||
#key-trade-mode-wrap.is-hidden,#key-rr-wrap.is-hidden{display:none!important}
|
.key-action-row .trailing-be-toggle{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--text-label);margin:0;cursor:pointer;user-select:none}
|
||||||
|
.key-action-row .trailing-be-toggle input{width:auto;margin:0;flex-shrink:0}
|
||||||
|
.key-trailing-hint{font-size:.72rem;margin:0;color:var(--text-muted);line-height:1.45}
|
||||||
|
.key-action-row .key-submit-btn{width:100%;padding:.65rem .75rem;font-size:.9rem;margin-top:.25rem}
|
||||||
|
#key-row-auto.is-hidden,#key-rr-wrap.is-hidden,#key-trade-mode-wrap.is-hidden,#key-direction-wrap.is-hidden,#key-trailing-wrap.is-hidden,#key-trailing-hint.is-hidden{display:none!important}
|
||||||
|
@media(max-width:720px){
|
||||||
|
.key-form-line.line-3{grid-template-columns:1fr 1fr}
|
||||||
|
.key-form-line.line-3 .key-field:first-child{grid-column:1/-1}
|
||||||
|
.key-form-line.line-2{grid-template-columns:1fr}
|
||||||
|
}
|
||||||
|
|||||||
+13
-4
@@ -5,26 +5,35 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var keyTimer = null;
|
var keyTimer = null;
|
||||||
var typeEl = document.getElementById('key-type');
|
var typeEl = document.getElementById('key-type');
|
||||||
|
var rowAuto = document.getElementById('key-row-auto');
|
||||||
var tradeModeWrap = document.getElementById('key-trade-mode-wrap');
|
var tradeModeWrap = document.getElementById('key-trade-mode-wrap');
|
||||||
|
var directionWrap = document.getElementById('key-direction-wrap');
|
||||||
var rrWrap = document.getElementById('key-rr-wrap');
|
var rrWrap = document.getElementById('key-rr-wrap');
|
||||||
var rrEl = document.getElementById('key-rr');
|
var rrEl = document.getElementById('key-rr');
|
||||||
var trailingWrap = document.getElementById('key-trailing-wrap');
|
var trailingWrap = document.getElementById('key-trailing-wrap');
|
||||||
|
var trailingHint = document.getElementById('key-trailing-hint');
|
||||||
var trailingEl = document.getElementById('key-trailing');
|
var trailingEl = document.getElementById('key-trailing');
|
||||||
var rowActions = document.getElementById('key-row-actions');
|
var directionEl = document.getElementById('key-direction');
|
||||||
var rowPrices = document.getElementById('key-row-prices');
|
|
||||||
|
|
||||||
function isAutoType(typ) {
|
function isAutoType(typ) {
|
||||||
return typ === '箱体突破' || typ === '收敛突破';
|
return typ === '箱体突破' || typ === '收敛突破';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBoxType(typ) {
|
||||||
|
return typ === '箱体突破';
|
||||||
|
}
|
||||||
|
|
||||||
function syncKeyForm() {
|
function syncKeyForm() {
|
||||||
var typ = typeEl ? typeEl.value : '';
|
var typ = typeEl ? typeEl.value : '';
|
||||||
var auto = isAutoType(typ);
|
var auto = isAutoType(typ);
|
||||||
|
var box = isBoxType(typ);
|
||||||
|
if (rowAuto) rowAuto.classList.toggle('is-hidden', !auto);
|
||||||
if (tradeModeWrap) tradeModeWrap.classList.toggle('is-hidden', !auto);
|
if (tradeModeWrap) tradeModeWrap.classList.toggle('is-hidden', !auto);
|
||||||
if (rrWrap) rrWrap.classList.toggle('is-hidden', !auto);
|
if (rrWrap) rrWrap.classList.toggle('is-hidden', !auto);
|
||||||
|
if (directionWrap) directionWrap.classList.toggle('is-hidden', !box);
|
||||||
if (trailingWrap) trailingWrap.classList.toggle('is-hidden', !auto);
|
if (trailingWrap) trailingWrap.classList.toggle('is-hidden', !auto);
|
||||||
if (rowActions) rowActions.classList.toggle('key-actions-zone', !auto);
|
if (trailingHint) trailingHint.classList.toggle('is-hidden', !auto);
|
||||||
if (rowPrices) rowPrices.classList.toggle('key-zone-mode', !auto);
|
if (directionEl) directionEl.disabled = !box;
|
||||||
if (!auto && trailingEl) trailingEl.checked = false;
|
if (!auto && trailingEl) trailingEl.checked = false;
|
||||||
if (auto && trailingEl && trailingEl.checked && rrEl) {
|
if (auto && trailingEl && trailingEl.checked && rrEl) {
|
||||||
if (parseFloat(rrEl.value) < 3) rrEl.value = '3';
|
if (parseFloat(rrEl.value) < 3) rrEl.value = '3';
|
||||||
|
|||||||
+80
-39
@@ -14,56 +14,88 @@
|
|||||||
<div class="key-rules-body">
|
<div class="key-rules-body">
|
||||||
<p><strong>箱体突破 / 收敛突破(自动单)</strong></p>
|
<p><strong>箱体突破 / 收敛突破(自动单)</strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>触发:<strong>5 分钟 K 线收盘</strong>收在上沿或下沿之外</li>
|
<li>触发:<strong>上周期 K 线收盘</strong>收在上沿或下沿之外(默认 5 分)</li>
|
||||||
|
<li>箱体突破须选<strong>上方向</strong>,仅当突破后下单方向一致时才自动开仓</li>
|
||||||
<li>顺势:上破做多、下破做空;反转:上破做空、下破做多</li>
|
<li>顺势:上破做多、下破做空;反转:上破做空、下破做多</li>
|
||||||
<li>自动<strong>市价开仓</strong>;止损 = 突破 K 线极值 ± 2 跳</li>
|
<li>自动<strong>市价开仓</strong>;止损 = 突破 K 线极值 ± 2 跳</li>
|
||||||
<li>盈亏比默认 2(可改);开启移动保本时默认 3,达目标价自动止盈</li>
|
<li>盈亏比默认 2(可改);开启移动保本时默认 3,达目标价自动止盈</li>
|
||||||
<li>成交后进入「下单监控」持仓,来源备注为监控类型</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<p><strong>关键支阻区(仅提醒)</strong></p>
|
<p><strong>关键支阻区(仅提醒)</strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>上沿 = 阻力,下沿 = 支撑,合并为一个区间</li>
|
<li>上沿 = 阻力,下沿 = 支撑,合并为一个区间</li>
|
||||||
<li>5m 收盘突破上沿或跌破下沿 → 微信推送(最多 3 次,间隔约 5 分钟)</li>
|
<li>上周期 K 线收盘突破 → 微信推送(最多 3 次,间隔约 5 分钟)</li>
|
||||||
<li>推送完毕后自动结案,<strong>不参与自动开仓</strong></li>
|
<li>推送完毕后自动结案,<strong>不参与自动开仓</strong></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<form action="{{ url_for('add_key') }}" method="post" class="form-compact" id="key-add-form">
|
<form action="{{ url_for('add_key') }}" method="post" id="key-add-form">
|
||||||
<div class="form-line line-3">
|
<div class="key-form-rows">
|
||||||
<div class="symbol-wrap symbol-mains">
|
<div class="key-form-line line-3">
|
||||||
<input type="text" class="symbol-input" placeholder="主力合约" autocomplete="off" required>
|
<div class="key-field symbol-wrap symbol-mains">
|
||||||
<input type="hidden" name="symbol" required>
|
<label class="text-label">品种</label>
|
||||||
<input type="hidden" name="symbol_name">
|
<input type="text" class="symbol-input" placeholder="输入中文或代码,选择主力合约" autocomplete="off" required>
|
||||||
<input type="hidden" name="market_code" required>
|
<input type="hidden" name="symbol" required>
|
||||||
<input type="hidden" name="sina_code">
|
<input type="hidden" name="symbol_name">
|
||||||
<div class="symbol-dropdown"></div>
|
<input type="hidden" name="market_code" required>
|
||||||
<div class="symbol-selected"></div>
|
<input type="hidden" name="sina_code">
|
||||||
|
<div class="symbol-dropdown"></div>
|
||||||
|
<div class="symbol-selected"></div>
|
||||||
|
</div>
|
||||||
|
<div class="key-field">
|
||||||
|
<label class="text-label">类型</label>
|
||||||
|
<select name="type" id="key-type" required>
|
||||||
|
<option value="箱体突破">箱体突破</option>
|
||||||
|
<option value="收敛突破">收敛突破</option>
|
||||||
|
<option value="关键支阻区">关键支阻区</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="key-field">
|
||||||
|
<label class="text-label">上周期</label>
|
||||||
|
<select name="bar_period" id="key-bar-period">
|
||||||
|
{% for p in key_periods %}
|
||||||
|
<option value="{{ p.key }}"{% if p.key == '5m' %} selected{% endif %}>{{ p.label }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<select name="type" id="key-type" required>
|
<div class="key-form-line line-3" id="key-row-auto">
|
||||||
<option value="箱体突破">箱体突破</option>
|
<div class="key-field" id="key-trade-mode-wrap">
|
||||||
<option value="收敛突破">收敛突破</option>
|
<label class="text-label">模式</label>
|
||||||
<option value="关键支阻区">关键支阻区</option>
|
<select name="trade_mode" id="key-trade-mode">
|
||||||
</select>
|
<option value="顺势">顺势</option>
|
||||||
<div id="key-trade-mode-wrap">
|
<option value="反转">反转</option>
|
||||||
<select name="trade_mode" id="key-trade-mode">
|
</select>
|
||||||
<option value="顺势">顺势</option>
|
</div>
|
||||||
<option value="反转">反转</option>
|
<div class="key-field" id="key-direction-wrap">
|
||||||
</select>
|
<label class="text-label">上方向</label>
|
||||||
|
<select name="direction" id="key-direction">
|
||||||
|
<option value="long">做多</option>
|
||||||
|
<option value="short">做空</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="key-field" id="key-rr-wrap">
|
||||||
|
<label class="text-label">盈亏比</label>
|
||||||
|
<input name="risk_reward" id="key-rr" type="number" step="0.1" min="0.5" max="10" value="2">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="key-form-line line-2" id="key-row-prices">
|
||||||
<div class="form-line line-3" id="key-row-prices">
|
<div class="key-field">
|
||||||
<input name="upper" type="number" step="0.0001" placeholder="上沿(阻力)" required>
|
<label class="text-label">上沿(阻力)</label>
|
||||||
<input name="lower" type="number" step="0.0001" placeholder="下沿(支撑)" required>
|
<input name="upper" type="number" step="0.0001" required>
|
||||||
<div id="key-rr-wrap">
|
</div>
|
||||||
<input name="risk_reward" id="key-rr" type="number" step="0.1" min="0.5" max="10" value="2" placeholder="盈亏比">
|
<div class="key-field">
|
||||||
|
<label class="text-label">下沿(支撑)</label>
|
||||||
|
<input name="lower" type="number" step="0.0001" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="key-action-row" id="key-row-actions">
|
||||||
|
<label class="trailing-be-toggle" id="key-trailing-wrap">
|
||||||
|
<input type="checkbox" name="trailing_be" id="key-trailing" value="1">
|
||||||
|
<span>移动保本</span>
|
||||||
|
</label>
|
||||||
|
<p class="hint key-trailing-hint" id="key-trailing-hint">开启后盈亏比默认 3,达 3R 自动止盈并启用移动止损</p>
|
||||||
|
<button type="submit" class="btn-primary key-submit-btn">添加</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="form-line line-key-actions" id="key-row-actions">
|
|
||||||
<label class="key-check" id="key-trailing-wrap">
|
|
||||||
<input type="checkbox" name="trailing_be" id="key-trailing" value="1">
|
|
||||||
<span class="key-check-text">移动保本(默认盈亏比 3,达 3R 止盈)</span>
|
|
||||||
</label>
|
|
||||||
<button type="submit" class="btn-primary key-submit-btn">添加</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<h3 class="section-label">监控列表</h3>
|
<h3 class="section-label">监控列表</h3>
|
||||||
@@ -72,8 +104,12 @@
|
|||||||
<div class="list-item key-item" data-key-id="{{ k.id }}" style="padding:.75rem;font-size:.85rem">
|
<div class="list-item key-item" data-key-id="{{ k.id }}" style="padding:.75rem;font-size:.85rem">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ k.symbol_name or k.symbol }}</strong> {{ k.monitor_type }}
|
<strong>{{ k.symbol_name or k.symbol }}</strong> {{ k.monitor_type }}
|
||||||
|
<span class="badge planned">{{ k.bar_period or '5m' }}</span>
|
||||||
{% if k.monitor_type in ('箱体突破', '收敛突破') %}
|
{% if k.monitor_type in ('箱体突破', '收敛突破') %}
|
||||||
<span class="badge planned">{{ k.trade_mode or '顺势' }}</span>
|
<span class="badge planned">{{ k.trade_mode or '顺势' }}</span>
|
||||||
|
{% if k.monitor_type == '箱体突破' and k.direction %}
|
||||||
|
<span class="badge profit">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if k.trailing_be %}
|
{% if k.trailing_be %}
|
||||||
<span class="badge profit">移动保本</span>
|
<span class="badge profit">移动保本</span>
|
||||||
@@ -101,15 +137,20 @@
|
|||||||
<h2>监控历史</h2>
|
<h2>监控历史</h2>
|
||||||
<div class="card-body card-scroll">
|
<div class="card-body card-scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th>品种</th><th>类型</th><th>模式</th><th>上沿</th><th>下沿</th><th>归档</th></tr></thead>
|
<thead><tr><th>品种</th><th>类型</th><th>周期</th><th>模式</th><th>上沿</th><th>下沿</th><th>归档</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for k in history %}
|
{% for k in history %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ k.symbol_name or k.symbol }}</td>
|
<td>{{ k.symbol_name or k.symbol }}</td>
|
||||||
<td>{{ k.monitor_type }}</td>
|
<td>{{ k.monitor_type }}</td>
|
||||||
|
<td>{{ k.bar_period or '5m' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if k.monitor_type in ('箱体突破', '收敛突破') %}
|
{% if k.monitor_type in ('箱体突破', '收敛突破') %}
|
||||||
{{ k.trade_mode or '顺势' }}{% if k.trailing_be %} · 移动保本{% endif %}
|
{{ k.trade_mode or '顺势' }}
|
||||||
|
{% if k.monitor_type == '箱体突破' and k.direction %}
|
||||||
|
· {{ '做多' if k.direction == 'long' else '做空' }}
|
||||||
|
{% endif %}
|
||||||
|
{% if k.trailing_be %} · 移动保本{% endif %}
|
||||||
{% elif k.monitor_type in ('关键阻力位', '关键支撑位') %}
|
{% elif k.monitor_type in ('关键阻力位', '关键支撑位') %}
|
||||||
支阻区
|
支阻区
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -121,7 +162,7 @@
|
|||||||
<td>{{ k.archived_at[:16] if k.archived_at else '' }}</td>
|
<td>{{ k.archived_at[:16] if k.archived_at else '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="6" class="text-muted">暂无历史</td></tr>
|
<tr><td colspan="7" class="text-muted">暂无历史</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user