开单计划增加决策理由;品种联想加速;复盘支持品种匹配
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -176,6 +176,7 @@ def init_db():
|
|||||||
"ALTER TABLE key_monitors ADD COLUMN market_code TEXT",
|
"ALTER TABLE key_monitors ADD COLUMN market_code TEXT",
|
||||||
"ALTER TABLE trade_records ADD COLUMN market_code TEXT",
|
"ALTER TABLE trade_records ADD COLUMN market_code TEXT",
|
||||||
"ALTER TABLE order_plans ADD COLUMN plan_date TEXT",
|
"ALTER TABLE order_plans ADD COLUMN plan_date TEXT",
|
||||||
|
"ALTER TABLE order_plans ADD COLUMN decision_reason TEXT",
|
||||||
"ALTER TABLE key_monitors ADD COLUMN status TEXT DEFAULT 'active'",
|
"ALTER TABLE key_monitors ADD COLUMN status TEXT DEFAULT 'active'",
|
||||||
"ALTER TABLE key_monitors ADD COLUMN archived_at TEXT",
|
"ALTER TABLE key_monitors ADD COLUMN archived_at TEXT",
|
||||||
"ALTER TABLE review_records ADD COLUMN direction TEXT",
|
"ALTER TABLE review_records ADD COLUMN direction TEXT",
|
||||||
@@ -188,6 +189,9 @@ def init_db():
|
|||||||
"ALTER TABLE review_records ADD COLUMN initial_pnl REAL",
|
"ALTER TABLE review_records ADD COLUMN initial_pnl REAL",
|
||||||
"ALTER TABLE review_records ADD COLUMN actual_pnl REAL",
|
"ALTER TABLE review_records ADD COLUMN actual_pnl REAL",
|
||||||
"ALTER TABLE review_records ADD COLUMN is_emotion INTEGER DEFAULT 0",
|
"ALTER TABLE review_records ADD COLUMN is_emotion INTEGER DEFAULT 0",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN symbol_name TEXT",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN market_code TEXT",
|
||||||
|
"ALTER TABLE review_records ADD COLUMN sina_code TEXT",
|
||||||
]
|
]
|
||||||
for sql in migrations:
|
for sql in migrations:
|
||||||
try:
|
try:
|
||||||
@@ -321,6 +325,7 @@ def check_order_plans():
|
|||||||
status = r["status"]
|
status = r["status"]
|
||||||
pid = r["id"]
|
pid = r["id"]
|
||||||
name = r["symbol_name"] or sym
|
name = r["symbol_name"] or sym
|
||||||
|
reason = r["decision_reason"] if "decision_reason" in r.keys() and r["decision_reason"] else "—"
|
||||||
|
|
||||||
# 计划状态:价格进入决策区间则激活并通知
|
# 计划状态:价格进入决策区间则激活并通知
|
||||||
if status == "planned":
|
if status == "planned":
|
||||||
@@ -330,6 +335,7 @@ def check_order_plans():
|
|||||||
f"【开单计划触发】{name} ({sym})\n"
|
f"【开单计划触发】{name} ({sym})\n"
|
||||||
f"方向:{'做多' if direction == 'long' else '做空'}\n"
|
f"方向:{'做多' if direction == 'long' else '做空'}\n"
|
||||||
f"决策区间:{zone_lower} ~ {zone_upper}\n"
|
f"决策区间:{zone_lower} ~ {zone_upper}\n"
|
||||||
|
f"决策理由:{reason}\n"
|
||||||
f"当前价:{p}\n"
|
f"当前价:{p}\n"
|
||||||
f"止损:{stop_loss} 止盈:{take_profit}"
|
f"止损:{stop_loss} 止盈:{take_profit}"
|
||||||
)
|
)
|
||||||
@@ -535,13 +541,14 @@ def add_plan():
|
|||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO order_plans
|
"""INSERT INTO order_plans
|
||||||
(symbol, symbol_name, market_code, sina_code, direction,
|
(symbol, symbol_name, market_code, sina_code, direction,
|
||||||
zone_upper, zone_lower, stop_loss, take_profit, plan_date)
|
zone_upper, zone_lower, stop_loss, take_profit, plan_date, decision_reason)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
symbol, symbol_name, market_code, sina_code, direction,
|
symbol, symbol_name, market_code, sina_code, direction,
|
||||||
float(d["zone_upper"]), float(d["zone_lower"]),
|
float(d["zone_upper"]), float(d["zone_lower"]),
|
||||||
float(d["stop_loss"]), float(d["take_profit"]),
|
float(d["stop_loss"]), float(d["take_profit"]),
|
||||||
today_str(),
|
today_str(),
|
||||||
|
d.get("decision_reason", "").strip(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -671,6 +678,14 @@ def add_review():
|
|||||||
flash("请选择离场触发")
|
flash("请选择离场触发")
|
||||||
return redirect(url_for("records"))
|
return redirect(url_for("records"))
|
||||||
|
|
||||||
|
symbol = d.get("symbol", "").strip()
|
||||||
|
symbol_name = d.get("symbol_name", "").strip()
|
||||||
|
market_code = d.get("market_code", "").strip()
|
||||||
|
sina_code = d.get("sina_code", "").strip()
|
||||||
|
if not symbol or not market_code:
|
||||||
|
flash("请从下拉列表选择品种(同花顺合约代码)")
|
||||||
|
return redirect(url_for("records"))
|
||||||
|
|
||||||
screenshot = ""
|
screenshot = ""
|
||||||
f = request.files.get("screenshot")
|
f = request.files.get("screenshot")
|
||||||
if f and f.filename:
|
if f and f.filename:
|
||||||
@@ -705,7 +720,7 @@ def add_review():
|
|||||||
if auto_kline and not screenshot:
|
if auto_kline and not screenshot:
|
||||||
try:
|
try:
|
||||||
generated = generate_review_kline_chart(
|
generated = generate_review_kline_chart(
|
||||||
symbol=d.get("symbol", "").strip(),
|
symbol=symbol,
|
||||||
periods=[d.get("kline_period1", "15m"), d.get("kline_period2", "1h")],
|
periods=[d.get("kline_period1", "15m"), d.get("kline_period2", "1h")],
|
||||||
count=int(d.get("kline_count") or 300),
|
count=int(d.get("kline_count") or 300),
|
||||||
cutoff_label=d.get("kline_cutoff", "平仓时间"),
|
cutoff_label=d.get("kline_cutoff", "平仓时间"),
|
||||||
@@ -725,17 +740,18 @@ def add_review():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO review_records
|
"""INSERT INTO review_records
|
||||||
(open_time, close_time, symbol, timeframe, direction,
|
(open_time, close_time, symbol, symbol_name, market_code, sina_code,
|
||||||
|
timeframe, direction,
|
||||||
entry_price, stop_loss, take_profit, close_price, lots,
|
entry_price, stop_loss, take_profit, close_price, lots,
|
||||||
holding_duration, initial_pnl, actual_pnl, pnl,
|
holding_duration, initial_pnl, actual_pnl, pnl,
|
||||||
open_type, expected_rr, actual_rr, exit_trigger, exit_supplement,
|
open_type, expected_rr, actual_rr, exit_trigger, exit_supplement,
|
||||||
watch_after_breakeven, new_position_while_occupied, screenshot,
|
watch_after_breakeven, new_position_while_occupied, screenshot,
|
||||||
auto_kline, kline_period1, kline_period2, kline_count, kline_cutoff,
|
auto_kline, kline_period1, kline_period2, kline_count, kline_cutoff,
|
||||||
behavior_tags, is_emotion, notes)
|
behavior_tags, is_emotion, notes)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
open_time, close_time,
|
open_time, close_time,
|
||||||
d.get("symbol", "").strip(),
|
symbol, symbol_name, market_code, sina_code,
|
||||||
d.get("timeframe", "").strip(),
|
d.get("timeframe", "").strip(),
|
||||||
direction,
|
direction,
|
||||||
entry_price, stop_loss, take_profit, close_price, lots,
|
entry_price, stop_loss, take_profit, close_price, lots,
|
||||||
|
|||||||
+42
-14
@@ -1,4 +1,14 @@
|
|||||||
(function () {
|
(function () {
|
||||||
|
function formatSub(item) {
|
||||||
|
return '同花顺 ' + item.ths_code +
|
||||||
|
(item.market_code ? ' · ' + item.market_code : '') +
|
||||||
|
' · ' + (item.exchange || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatInputLabel(item) {
|
||||||
|
return item.input_label || (item.name + ' ' + item.ths_code);
|
||||||
|
}
|
||||||
|
|
||||||
function initSymbolInput(wrapper) {
|
function initSymbolInput(wrapper) {
|
||||||
const input = wrapper.querySelector('.symbol-input');
|
const input = wrapper.querySelector('.symbol-input');
|
||||||
const hiddenThs = wrapper.querySelector('input[name="symbol"]');
|
const hiddenThs = wrapper.querySelector('input[name="symbol"]');
|
||||||
@@ -8,19 +18,21 @@
|
|||||||
const dropdown = wrapper.querySelector('.symbol-dropdown');
|
const dropdown = wrapper.querySelector('.symbol-dropdown');
|
||||||
const selectedEl = wrapper.querySelector('.symbol-selected');
|
const selectedEl = wrapper.querySelector('.symbol-selected');
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
let abortCtrl = null;
|
||||||
|
const cache = new Map();
|
||||||
|
|
||||||
function hideDropdown() {
|
function hideDropdown() {
|
||||||
dropdown.classList.remove('show');
|
dropdown.classList.remove('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectItem(item) {
|
function selectItem(item) {
|
||||||
input.value = item.name;
|
const label = formatInputLabel(item);
|
||||||
|
input.value = label;
|
||||||
hiddenThs.value = item.ths_code;
|
hiddenThs.value = item.ths_code;
|
||||||
hiddenName.value = item.name;
|
hiddenName.value = item.name;
|
||||||
if (hiddenMarket) hiddenMarket.value = item.market_code || '';
|
if (hiddenMarket) hiddenMarket.value = item.market_code || '';
|
||||||
if (hiddenSina) hiddenSina.value = item.sina_code || '';
|
if (hiddenSina) hiddenSina.value = item.sina_code || '';
|
||||||
selectedEl.textContent = '同花顺: ' + item.ths_code +
|
selectedEl.textContent = formatSub(item);
|
||||||
(item.market_code ? ' (' + item.market_code + ')' : '');
|
|
||||||
hideDropdown();
|
hideDropdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +45,7 @@
|
|||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'symbol-option';
|
div.className = 'symbol-option';
|
||||||
div.innerHTML = item.display +
|
div.innerHTML = item.display +
|
||||||
'<div class="sub">同花顺 ' + item.ths_code +
|
'<div class="sub">' + formatSub(item) + '</div>';
|
||||||
(item.market_code ? ' · ' + item.market_code : '') +
|
|
||||||
' · ' + item.exchange + '</div>';
|
|
||||||
div.addEventListener('mousedown', function (e) {
|
div.addEventListener('mousedown', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
selectItem(item);
|
selectItem(item);
|
||||||
@@ -46,6 +56,29 @@
|
|||||||
dropdown.classList.add('show');
|
dropdown.classList.add('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function search(q) {
|
||||||
|
if (cache.has(q)) {
|
||||||
|
renderItems(cache.get(q));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (abortCtrl) {
|
||||||
|
abortCtrl.abort();
|
||||||
|
}
|
||||||
|
abortCtrl = new AbortController();
|
||||||
|
fetch('/api/symbols/search?q=' + encodeURIComponent(q), {
|
||||||
|
signal: abortCtrl.signal,
|
||||||
|
})
|
||||||
|
.then(function (r) { return r.json(); })
|
||||||
|
.then(function (items) {
|
||||||
|
cache.set(q, items);
|
||||||
|
renderItems(items);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
if (err && err.name === 'AbortError') return;
|
||||||
|
hideDropdown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
input.addEventListener('input', function () {
|
input.addEventListener('input', function () {
|
||||||
hiddenThs.value = '';
|
hiddenThs.value = '';
|
||||||
hiddenName.value = '';
|
hiddenName.value = '';
|
||||||
@@ -59,11 +92,8 @@
|
|||||||
}
|
}
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = setTimeout(function () {
|
timer = setTimeout(function () {
|
||||||
fetch('/api/symbols/search?q=' + encodeURIComponent(q))
|
search(q);
|
||||||
.then(function (r) { return r.json(); })
|
}, 120);
|
||||||
.then(renderItems)
|
|
||||||
.catch(function () { hideDropdown(); });
|
|
||||||
}, 300);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('blur', function () {
|
input.addEventListener('blur', function () {
|
||||||
@@ -73,9 +103,7 @@
|
|||||||
input.addEventListener('focus', function () {
|
input.addEventListener('focus', function () {
|
||||||
const q = input.value.trim();
|
const q = input.value.trim();
|
||||||
if (q && !hiddenThs.value) {
|
if (q && !hiddenThs.value) {
|
||||||
fetch('/api/symbols/search?q=' + encodeURIComponent(q))
|
search(q);
|
||||||
.then(function (r) { return r.json(); })
|
|
||||||
.then(renderItems);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-13
@@ -3,7 +3,9 @@
|
|||||||
展示同花顺合约代码(ag2608);行情默认新浪,机构用户可通过环境变量启用同花顺 iFinD。
|
展示同花顺合约代码(ag2608);行情默认新浪,机构用户可通过环境变量启用同花顺 iFinD。
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -64,6 +66,9 @@ PRODUCTS = [
|
|||||||
|
|
||||||
_MAIN_CACHE: dict[str, tuple[float, dict]] = {}
|
_MAIN_CACHE: dict[str, tuple[float, dict]] = {}
|
||||||
_CACHE_TTL = 300
|
_CACHE_TTL = 300
|
||||||
|
_main_index_lock = threading.Lock()
|
||||||
|
_main_index: dict[str, dict] = {}
|
||||||
|
_main_index_ts = 0.0
|
||||||
|
|
||||||
|
|
||||||
def build_ths_code(product: dict, year: int, month: int) -> str:
|
def build_ths_code(product: dict, year: int, month: int) -> str:
|
||||||
@@ -164,14 +169,16 @@ def ths_to_sina_code(ths_code: str) -> Optional[str]:
|
|||||||
|
|
||||||
def _make_symbol_item(product: dict, year: int, month: int, volume: float) -> dict:
|
def _make_symbol_item(product: dict, year: int, month: int, volume: float) -> dict:
|
||||||
ths = build_ths_code(product, year, month)
|
ths = build_ths_code(product, year, month)
|
||||||
|
name = product["name"]
|
||||||
return {
|
return {
|
||||||
"name": product["name"],
|
"name": name,
|
||||||
"ths_code": ths,
|
"ths_code": ths,
|
||||||
"market_code": build_ths_full_code(product, year, month),
|
"market_code": build_ths_full_code(product, year, month),
|
||||||
"sina_code": build_sina_code(product, year, month),
|
"sina_code": build_sina_code(product, year, month),
|
||||||
"exchange": product["exchange"],
|
"exchange": product["exchange"],
|
||||||
"contract": f"主力 {ths}",
|
"contract": f"主力 {ths}",
|
||||||
"display": f"{product['name']} 主力 {ths}",
|
"display": f"{name} 主力 {ths}",
|
||||||
|
"input_label": f"{name} {ths}",
|
||||||
"volume": volume,
|
"volume": volume,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +225,7 @@ def resolve_main_contract(product: dict) -> Optional[dict]:
|
|||||||
"exchange": product["exchange"],
|
"exchange": product["exchange"],
|
||||||
"contract": f"主力连续 {ths_main}",
|
"contract": f"主力连续 {ths_main}",
|
||||||
"display": f"{product['name']} 主力连续 {ths_main}",
|
"display": f"{product['name']} 主力连续 {ths_main}",
|
||||||
|
"input_label": f"{product['name']} {ths_main}",
|
||||||
"volume": raw.get("volume", 0),
|
"volume": raw.get("volume", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,26 +234,110 @@ def resolve_main_contract(product: dict) -> Optional[dict]:
|
|||||||
return best
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def _enrich_item(item: dict) -> dict:
|
||||||
|
out = dict(item)
|
||||||
|
if not out.get("input_label"):
|
||||||
|
out["input_label"] = f"{out.get('name', '')} {out.get('ths_code', '')}".strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_main_index():
|
||||||
|
"""后台预热全部品种主力合约,搜索时只读本地缓存。"""
|
||||||
|
global _main_index, _main_index_ts
|
||||||
|
new_idx: dict[str, dict] = {}
|
||||||
|
with ThreadPoolExecutor(max_workers=10) as pool:
|
||||||
|
futures = {pool.submit(resolve_main_contract, p): p for p in PRODUCTS}
|
||||||
|
for fut in as_completed(futures):
|
||||||
|
product = futures[fut]
|
||||||
|
try:
|
||||||
|
main = fut.result()
|
||||||
|
if main:
|
||||||
|
new_idx[product["sina"]] = _enrich_item(main)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
with _main_index_lock:
|
||||||
|
_main_index = new_idx
|
||||||
|
_main_index_ts = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
def _warm_loop():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
refresh_main_index()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(_CACHE_TTL)
|
||||||
|
|
||||||
|
|
||||||
|
def _start_warm_thread():
|
||||||
|
threading.Thread(target=_warm_loop, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
|
def _stub_main_contract(product: dict) -> dict:
|
||||||
|
"""缓存未就绪时的快速占位(当月合约),避免首次打开搜索为空。"""
|
||||||
|
today = date.today()
|
||||||
|
return _enrich_item(_make_symbol_item(product, today.year, today.month, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def _product_matches(product: dict, q_lower: str) -> bool:
|
||||||
|
name_lower = product["name"].lower()
|
||||||
|
if q_lower in name_lower:
|
||||||
|
return True
|
||||||
|
if len(q_lower) >= 2:
|
||||||
|
ths_lower = product["ths"].lower()
|
||||||
|
sina_lower = product["sina"].lower()
|
||||||
|
if q_lower in ths_lower or q_lower in sina_lower:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _match_score(product: dict, q_lower: str) -> int:
|
||||||
|
name_lower = product["name"].lower()
|
||||||
|
if name_lower == q_lower:
|
||||||
|
return 200
|
||||||
|
if name_lower.startswith(q_lower):
|
||||||
|
return 150
|
||||||
|
if q_lower in name_lower:
|
||||||
|
return 100
|
||||||
|
ths_lower = product["ths"].lower()
|
||||||
|
if ths_lower == q_lower:
|
||||||
|
return 90
|
||||||
|
if ths_lower.startswith(q_lower):
|
||||||
|
return 70
|
||||||
|
if product["sina"].lower() == q_lower:
|
||||||
|
return 80
|
||||||
|
return 10
|
||||||
|
|
||||||
|
|
||||||
def search_symbols(query: str) -> list:
|
def search_symbols(query: str) -> list:
|
||||||
q = query.strip().lower()
|
q = query.strip()
|
||||||
if not q:
|
if not q:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
results = []
|
q_lower = q.lower()
|
||||||
|
with _main_index_lock:
|
||||||
|
index = dict(_main_index)
|
||||||
|
index_ready = bool(index)
|
||||||
|
|
||||||
|
scored: list[tuple[int, dict]] = []
|
||||||
for p in PRODUCTS:
|
for p in PRODUCTS:
|
||||||
name = p["name"]
|
if not _product_matches(p, q_lower):
|
||||||
if q not in name.lower() and q not in p["ths"].lower() and q not in p["sina"].lower():
|
|
||||||
continue
|
continue
|
||||||
main = resolve_main_contract(p)
|
main = index.get(p["sina"])
|
||||||
|
if not main and not index_ready:
|
||||||
|
main = _stub_main_contract(p)
|
||||||
if main:
|
if main:
|
||||||
results.append(main)
|
scored.append((_match_score(p, q_lower), main))
|
||||||
|
|
||||||
|
scored.sort(key=lambda x: -x[0])
|
||||||
|
results = [item for _, item in scored[:12]]
|
||||||
|
|
||||||
if not results and len(q) >= 3:
|
if not results and len(q) >= 3:
|
||||||
codes = ths_to_codes(query.strip())
|
codes = ths_to_codes(q)
|
||||||
if codes:
|
if codes:
|
||||||
raw = fetch_raw_for_volume(codes["sina_code"])
|
raw = fetch_raw_for_volume(codes["sina_code"])
|
||||||
name = raw["name"] if raw else query.strip()
|
name = raw["name"] if raw else q
|
||||||
results.append({
|
results.append(_enrich_item({
|
||||||
"name": name,
|
"name": name,
|
||||||
"ths_code": codes["ths_code"],
|
"ths_code": codes["ths_code"],
|
||||||
"market_code": codes["market_code"],
|
"market_code": codes["market_code"],
|
||||||
@@ -254,9 +346,12 @@ def search_symbols(query: str) -> list:
|
|||||||
"contract": codes["ths_code"],
|
"contract": codes["ths_code"],
|
||||||
"display": f"{name} ({codes['ths_code']})",
|
"display": f"{name} ({codes['ths_code']})",
|
||||||
"volume": raw.get("volume", 0) if raw else 0,
|
"volume": raw.get("volume", 0) if raw else 0,
|
||||||
})
|
}))
|
||||||
|
|
||||||
return results[:12]
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
_start_warm_thread()
|
||||||
|
|
||||||
|
|
||||||
def get_price(market_code: str, sina_code: str = "") -> Optional[float]:
|
def get_price(market_code: str, sina_code: str = "") -> Optional[float]:
|
||||||
|
|||||||
+13
-7
@@ -10,7 +10,7 @@
|
|||||||
<form action="{{ url_for('add_plan') }}" method="post" class="form-compact">
|
<form action="{{ url_for('add_plan') }}" method="post" class="form-compact">
|
||||||
<div class="form-line line-3">
|
<div class="form-line line-3">
|
||||||
<div class="symbol-wrap">
|
<div class="symbol-wrap">
|
||||||
<input type="text" class="symbol-input" placeholder="中文名或同花顺代码" autocomplete="off" required>
|
<input type="text" class="symbol-input" placeholder="品种" autocomplete="off" required>
|
||||||
<input type="hidden" name="symbol" required>
|
<input type="hidden" name="symbol" required>
|
||||||
<input type="hidden" name="symbol_name">
|
<input type="hidden" name="symbol_name">
|
||||||
<input type="hidden" name="market_code" required>
|
<input type="hidden" name="market_code" required>
|
||||||
@@ -23,10 +23,11 @@
|
|||||||
<option value="long">做多</option>
|
<option value="long">做多</option>
|
||||||
<option value="short">做空</option>
|
<option value="short">做空</option>
|
||||||
</select>
|
</select>
|
||||||
<input name="zone_lower" type="number" step="0.0001" placeholder="区间下限" required>
|
<input name="decision_reason" type="text" placeholder="决策理由">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-line line-3">
|
<div class="form-line line-4">
|
||||||
<input name="zone_upper" type="number" step="0.0001" placeholder="区间上限" required>
|
<input name="zone_lower" type="number" step="0.0001" placeholder="决策区间下限" required>
|
||||||
|
<input name="zone_upper" type="number" step="0.0001" placeholder="决策区间上限" required>
|
||||||
<input name="stop_loss" type="number" step="0.0001" placeholder="止损" required>
|
<input name="stop_loss" type="number" step="0.0001" placeholder="止损" required>
|
||||||
<input name="take_profit" type="number" step="0.0001" placeholder="止盈" required>
|
<input name="take_profit" type="number" step="0.0001" placeholder="止盈" required>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +45,11 @@
|
|||||||
{% if p.status == 'planned' %}<span class="badge planned">待触发</span>
|
{% if p.status == 'planned' %}<span class="badge planned">待触发</span>
|
||||||
{% else %}<span class="badge active">已激活</span>{% endif %}
|
{% else %}<span class="badge active">已激活</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div>{{ p.zone_lower }}~{{ p.zone_upper }} 损{{ p.stop_loss }} 盈{{ p.take_profit }}</div>
|
<div>
|
||||||
|
区间{{ p.zone_lower }}~{{ p.zone_upper }}
|
||||||
|
{% if p.decision_reason %} · {{ p.decision_reason }}{% endif %}
|
||||||
|
· 损{{ p.stop_loss }} 盈{{ p.take_profit }}
|
||||||
|
</div>
|
||||||
<a href="{{ url_for('del_plan', pid=p.id) }}" class="btn-del" onclick="return confirm('删除?')">删</a>
|
<a href="{{ url_for('del_plan', pid=p.id) }}" class="btn-del" onclick="return confirm('删除?')">删</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -65,7 +70,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<div class="card-scroll">
|
<div class="card-scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><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></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for p in history %}
|
{% for p in history %}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -73,6 +78,7 @@
|
|||||||
<td>{{ p.symbol_name or p.symbol }}</td>
|
<td>{{ p.symbol_name or p.symbol }}</td>
|
||||||
<td><span class="badge dir">{{ '多' if p.direction == 'long' else '空' }}</span></td>
|
<td><span class="badge dir">{{ '多' if p.direction == 'long' else '空' }}</span></td>
|
||||||
<td>{{ p.zone_lower }}~{{ p.zone_upper }}</td>
|
<td>{{ p.zone_lower }}~{{ p.zone_upper }}</td>
|
||||||
|
<td>{{ p.decision_reason or '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if p.status == 'closed' %}<span class="badge profit">完成</span>
|
{% if p.status == 'closed' %}<span class="badge profit">完成</span>
|
||||||
{% elif p.status == 'expired' %}<span class="badge expired">失效</span>
|
{% elif p.status == 'expired' %}<span class="badge expired">失效</span>
|
||||||
@@ -80,7 +86,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="5" class="text-muted">暂无历史</td></tr>
|
<tr><td colspan="6" class="text-muted">暂无历史</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
+11
-3
@@ -7,7 +7,15 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="review-form" action="{{ url_for('add_review') }}" method="post" enctype="multipart/form-data" class="form-compact form-compact-review line-tight">
|
<form id="review-form" action="{{ url_for('add_review') }}" method="post" enctype="multipart/form-data" class="form-compact form-compact-review line-tight">
|
||||||
<div class="form-line line-4">
|
<div class="form-line line-4">
|
||||||
<input name="symbol" placeholder="品种 ag2608" required>
|
<div class="symbol-wrap">
|
||||||
|
<input type="text" class="symbol-input" placeholder="品种" autocomplete="off" required>
|
||||||
|
<input type="hidden" name="symbol" required>
|
||||||
|
<input type="hidden" name="symbol_name">
|
||||||
|
<input type="hidden" name="market_code" required>
|
||||||
|
<input type="hidden" name="sina_code">
|
||||||
|
<div class="symbol-dropdown"></div>
|
||||||
|
<div class="symbol-selected"></div>
|
||||||
|
</div>
|
||||||
<select name="direction" required>
|
<select name="direction" required>
|
||||||
<option value="">方向</option>
|
<option value="">方向</option>
|
||||||
<option value="long">做多</option>
|
<option value="long">做多</option>
|
||||||
@@ -101,7 +109,7 @@
|
|||||||
{% for r in reviews %}
|
{% for r in reviews %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ r.close_time[:16] if r.close_time else '' }}</td>
|
<td>{{ r.close_time[:16] if r.close_time else '' }}</td>
|
||||||
<td>{{ r.symbol }}</td>
|
<td>{{ r.symbol_name or r.symbol }}</td>
|
||||||
<td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td>
|
<td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td>
|
||||||
<td>
|
<td>
|
||||||
{% if r.pnl and r.pnl > 0 %}<span class="badge profit">{{ r.pnl }}</span>
|
{% if r.pnl and r.pnl > 0 %}<span class="badge profit">{{ r.pnl }}</span>
|
||||||
@@ -111,7 +119,7 @@
|
|||||||
<td>{% if r.is_emotion %}<span class="badge loss">情绪</span>{% else %}-{% endif %}</td>
|
<td>{% if r.is_emotion %}<span class="badge loss">情绪</span>{% else %}-{% endif %}</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn-link review-view-btn" data-review='{{ {
|
<button type="button" class="btn-link review-view-btn" data-review='{{ {
|
||||||
"symbol": r.symbol, "direction": "做多" if r.direction=="long" else "做空",
|
"symbol": r.symbol_name or r.symbol, "symbol_code": r.symbol, "direction": "做多" if r.direction=="long" else "做空",
|
||||||
"entry_price": r.entry_price, "stop_loss": r.stop_loss, "take_profit": r.take_profit,
|
"entry_price": r.entry_price, "stop_loss": r.stop_loss, "take_profit": r.take_profit,
|
||||||
"close_price": r.close_price, "lots": r.lots,
|
"close_price": r.close_price, "lots": r.lots,
|
||||||
"open_time": r.open_time, "close_time": r.close_time,
|
"open_time": r.open_time, "close_time": r.close_time,
|
||||||
|
|||||||
Reference in New Issue
Block a user