修复k线图开平仓

This commit is contained in:
dekun
2026-05-21 10:20:50 +08:00
parent 4f0243e4fe
commit 2301590a97
4 changed files with 293 additions and 59 deletions
+117 -13
View File
@@ -625,6 +625,67 @@ def _local_input_datetime_to_ms(dt_text):
return None
def _marker_tag_label(tag):
t = str(tag or "").strip().upper()
if t == "ENTRY":
return "开仓"
if t == "EXIT":
return "平仓"
return str(tag or "")
def _timeframe_period_ms(tf):
s = (tf or "").strip().lower()
if s.endswith("m"):
try:
return int(s[:-1]) * 60 * 1000
except ValueError:
pass
if s.endswith("h"):
try:
return int(s[:-1]) * 3600 * 1000
except ValueError:
pass
if s.endswith("d"):
try:
return int(s[:-1]) * 86400 * 1000
except ValueError:
pass
return 300000
def _ohlcv_dict_rows_to_lists(rows, lim):
if not rows:
return []
pick = rows[-lim:] if len(rows) >= lim else rows
return [[r["ts"], r["o"], r["h"], r["l"], r["c"], r.get("v", 0)] for r in pick]
def _fetch_ohlcv_ending_at(exchange_symbol, timeframe, limit, end_ts_ms):
"""以 end_ts_ms 为终点向前取 K 线(无 end 则拉最近 limit 根)。"""
lim = max(2, int(limit or ORDER_CHART_LIMIT))
try:
if not end_ts_ms:
ohlcv = exchange.fetch_ohlcv(exchange_symbol, timeframe=timeframe, limit=lim)
else:
period = _timeframe_period_ms(timeframe)
since = int(end_ts_ms) - period * (lim + 10)
ohlcv = exchange.fetch_ohlcv(
exchange_symbol, timeframe=timeframe, since=max(0, since), limit=lim + 20
)
except Exception:
return []
rows = _ohlcv_to_rows(ohlcv)
if not rows:
return []
if not end_ts_ms:
return _ohlcv_dict_rows_to_lists(rows, lim)
filtered = [r for r in rows if int(r["ts"]) <= int(end_ts_ms)]
if len(filtered) >= 2:
return _ohlcv_dict_rows_to_lists(filtered, lim)
return _ohlcv_dict_rows_to_lists(rows, lim)
def _pick_marker_point(rows, target_ts_ms, target_price=None):
if not rows or target_ts_ms is None:
return None, None
@@ -679,7 +740,7 @@ def _render_candles_subplot(rows, title, width, height, bg_rgb=(255, 255, 255),
continue
if idx < 0 or idx >= n:
continue
marker_by_idx[idx] = mp
marker_by_idx.setdefault(idx, []).append(mp)
x0 = pad_l
for i, r in enumerate(rows):
@@ -706,27 +767,29 @@ def _render_candles_subplot(rows, title, width, height, bg_rgb=(255, 255, 255),
draw.rectangle((left, top, left + body_w, bot), fill=(255, 255, 255), outline=edge_color, width=1)
else:
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
if i in marker_by_idx:
mp = marker_by_idx[i]
for j, mp in enumerate(marker_by_idx.get(i, [])):
tag = str(mp.get("tag") or "")
label = _marker_tag_label(tag)
m_price = float(mp.get("price") or r["c"])
y_m = pad_t + int((hi - m_price) / (hi - lo) * plot_h)
y_m = max(pad_t + 4, min(pad_t + plot_h - 4, y_m))
x_off = (j - (len(marker_by_idx[i]) - 1) / 2.0) * 14
x_draw = int(x_mid + x_off)
if tag == "ENTRY":
m_color = (0, 195, 95)
tri = [(x_mid, y_m - 20), (x_mid - 9, y_m - 4), (x_mid + 9, y_m - 4)]
tri = [(x_draw, y_m - 20), (x_draw - 9, y_m - 4), (x_draw + 9, y_m - 4)]
text_y = y_m - 36
else:
m_color = (235, 65, 65)
tri = [(x_mid, y_m + 20), (x_mid - 9, y_m + 4), (x_mid + 9, y_m + 4)]
tri = [(x_draw, y_m + 20), (x_draw - 9, y_m + 4), (x_draw + 9, y_m + 4)]
text_y = y_m + 12
draw.ellipse((x_mid - 5, y_m - 5, x_mid + 5, y_m + 5), fill=m_color, outline=(255, 255, 255), width=1)
draw.ellipse((x_draw - 5, y_m - 5, x_draw + 5, y_m + 5), fill=m_color, outline=(255, 255, 255), width=1)
draw.polygon(tri, fill=m_color)
draw.line((x_mid, y_m, x_mid, y_m - 16 if tag == "ENTRY" else y_m + 16), fill=m_color, width=3)
draw.line((x_draw, y_m, x_draw, y_m - 16 if tag == "ENTRY" else y_m + 16), fill=m_color, width=3)
if font:
draw.text((x_mid + 8, text_y), tag, fill=m_color, font=font)
draw.text((x_draw + 8, text_y), label, fill=m_color, font=font)
else:
draw.text((x_mid + 8, text_y), tag, fill=m_color)
draw.text((x_draw + 8, text_y), label, fill=m_color)
x0 = x1
if len(marker_points or []) >= 2:
@@ -778,16 +841,31 @@ def generate_multi_timeframe_chart_png(
ensure_markets_loaded()
panels = []
cell_w, cell_h = 980, 520
end_ts_ms = None
if marker_payload:
try:
end_ts_ms = int(marker_payload.get("exit_ts_ms") or marker_payload.get("entry_ts_ms") or 0) or None
except (TypeError, ValueError):
end_ts_ms = None
default_marker_tfs = {str(t).strip().lower() for t in timeframes}
for tf in timeframes:
try:
ohlcv = exchange.fetch_ohlcv(exchange_symbol, timeframe=tf, limit=limit)
ohlcv = _fetch_ohlcv_ending_at(exchange_symbol, tf, limit, end_ts_ms)
if not ohlcv and end_ts_ms:
ohlcv = exchange.fetch_ohlcv(exchange_symbol, timeframe=tf, limit=limit)
except Exception:
ohlcv = []
rows = _ohlcv_to_rows(ohlcv)[-limit:]
title = f"{title_prefix} | {tf} x{len(rows)}"
points = []
tf_key = str(tf).strip().lower()
marker_tfs = {str(x).strip().lower() for x in (marker_timeframes or []) if str(x).strip()}
if marker_payload:
if marker_timeframes:
marker_tfs = {str(x).strip().lower() for x in marker_timeframes if str(x).strip()}
else:
marker_tfs = default_marker_tfs
else:
marker_tfs = set()
if marker_payload and tf_key in marker_tfs:
entry_idx, entry_price = _pick_marker_point(rows, marker_payload.get("entry_ts_ms"), marker_payload.get("entry_price"))
exit_idx, exit_price = _pick_marker_point(rows, marker_payload.get("exit_ts_ms"), marker_payload.get("exit_price"))
@@ -845,7 +923,26 @@ def generate_multi_timeframe_chart_png(
return fname
def generate_order_open_chart(exchange_symbol, title_prefix, timeframes=None, limit=None):
def generate_order_open_chart(
exchange_symbol,
title_prefix,
timeframes=None,
limit=None,
opened_at_ms=None,
entry_price=None,
):
marker_payload = None
if opened_at_ms:
marker_payload = {
"entry_ts_ms": opened_at_ms,
"exit_ts_ms": None,
"entry_price": entry_price,
"exit_price": None,
}
marker_tfs = (
{x.strip().lower() for x in (timeframes or ORDER_CHART_TFS) if x and str(x).strip()}
or {"5m", "15m", "1h", "4h"}
)
return generate_multi_timeframe_chart_png(
exchange_symbol,
title_prefix,
@@ -854,6 +951,8 @@ def generate_order_open_chart(exchange_symbol, title_prefix, timeframes=None, li
out_dir=ORDER_CHART_DIR,
filename=None,
filename_prefix="order",
marker_payload=marker_payload,
marker_timeframes=marker_tfs,
)
@@ -5925,7 +6024,12 @@ def add_order():
if make_order_chart and ORDER_CHART_ENABLED:
try:
title_prefix = f"{symbol} {direction} #{new_order_id}"
chart_name = generate_order_open_chart(exchange_symbol, title_prefix)
chart_name = generate_order_open_chart(
exchange_symbol,
title_prefix,
opened_at_ms=opened_at_ms,
entry_price=trigger_price,
)
if chart_name:
chart_url = f"/static/images/order_charts/{chart_name}"
except Exception: