修复k线图开平仓
This commit is contained in:
@@ -642,6 +642,15 @@ def _local_input_datetime_to_ms(dt_text):
|
|||||||
return None
|
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 _pick_marker_point(rows, target_ts_ms, target_price=None):
|
def _pick_marker_point(rows, target_ts_ms, target_price=None):
|
||||||
if not rows or target_ts_ms is None:
|
if not rows or target_ts_ms is None:
|
||||||
return None, None
|
return None, None
|
||||||
@@ -696,7 +705,7 @@ def _render_candles_subplot(rows, title, width, height, bg_rgb=(255, 255, 255),
|
|||||||
continue
|
continue
|
||||||
if idx < 0 or idx >= n:
|
if idx < 0 or idx >= n:
|
||||||
continue
|
continue
|
||||||
marker_by_idx[idx] = mp
|
marker_by_idx.setdefault(idx, []).append(mp)
|
||||||
|
|
||||||
x0 = pad_l
|
x0 = pad_l
|
||||||
for i, r in enumerate(rows):
|
for i, r in enumerate(rows):
|
||||||
@@ -723,27 +732,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)
|
draw.rectangle((left, top, left + body_w, bot), fill=(255, 255, 255), outline=edge_color, width=1)
|
||||||
else:
|
else:
|
||||||
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
||||||
if i in marker_by_idx:
|
for j, mp in enumerate(marker_by_idx.get(i, [])):
|
||||||
mp = marker_by_idx[i]
|
|
||||||
tag = str(mp.get("tag") or "")
|
tag = str(mp.get("tag") or "")
|
||||||
|
label = _marker_tag_label(tag)
|
||||||
m_price = float(mp.get("price") or r["c"])
|
m_price = float(mp.get("price") or r["c"])
|
||||||
y_m = pad_t + int((hi - m_price) / (hi - lo) * plot_h)
|
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))
|
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":
|
if tag == "ENTRY":
|
||||||
m_color = (0, 195, 95)
|
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
|
text_y = y_m - 36
|
||||||
else:
|
else:
|
||||||
m_color = (235, 65, 65)
|
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
|
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.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:
|
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:
|
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
|
x0 = x1
|
||||||
|
|
||||||
if len(marker_points or []) >= 2:
|
if len(marker_points or []) >= 2:
|
||||||
@@ -853,6 +864,7 @@ def generate_multi_timeframe_chart_png(
|
|||||||
end_ts_ms = int(marker_payload.get("exit_ts_ms") or marker_payload.get("entry_ts_ms") or 0) or None
|
end_ts_ms = int(marker_payload.get("exit_ts_ms") or marker_payload.get("entry_ts_ms") or 0) or None
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
end_ts_ms = None
|
end_ts_ms = None
|
||||||
|
default_marker_tfs = {str(t).strip().lower() for t in timeframes}
|
||||||
for tf in timeframes:
|
for tf in timeframes:
|
||||||
try:
|
try:
|
||||||
ohlcv = _fetch_ohlcv_ending_at(exchange_symbol, tf, limit, end_ts_ms)
|
ohlcv = _fetch_ohlcv_ending_at(exchange_symbol, tf, limit, end_ts_ms)
|
||||||
@@ -864,7 +876,13 @@ def generate_multi_timeframe_chart_png(
|
|||||||
title = f"{title_prefix} | {tf} x{len(rows)}"
|
title = f"{title_prefix} | {tf} x{len(rows)}"
|
||||||
points = []
|
points = []
|
||||||
tf_key = str(tf).strip().lower()
|
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:
|
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"))
|
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"))
|
exit_idx, exit_price = _pick_marker_point(rows, marker_payload.get("exit_ts_ms"), marker_payload.get("exit_price"))
|
||||||
@@ -922,7 +940,26 @@ def generate_multi_timeframe_chart_png(
|
|||||||
return fname
|
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(
|
return generate_multi_timeframe_chart_png(
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
title_prefix,
|
title_prefix,
|
||||||
@@ -931,6 +968,8 @@ def generate_order_open_chart(exchange_symbol, title_prefix, timeframes=None, li
|
|||||||
out_dir=ORDER_CHART_DIR,
|
out_dir=ORDER_CHART_DIR,
|
||||||
filename=None,
|
filename=None,
|
||||||
filename_prefix="order",
|
filename_prefix="order",
|
||||||
|
marker_payload=marker_payload,
|
||||||
|
marker_timeframes=marker_tfs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -6572,7 +6611,12 @@ def add_order():
|
|||||||
if make_order_chart and ORDER_CHART_ENABLED:
|
if make_order_chart and ORDER_CHART_ENABLED:
|
||||||
try:
|
try:
|
||||||
title_prefix = f"{symbol} {direction} #{new_order_id}"
|
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:
|
if chart_name:
|
||||||
chart_url = f"/static/images/order_charts/{chart_name}"
|
chart_url = f"/static/images/order_charts/{chart_name}"
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -7147,10 +7191,9 @@ def add_journal():
|
|||||||
symbol_guess = normalize_symbol_input(coin) or coin
|
symbol_guess = normalize_symbol_input(coin) or coin
|
||||||
exchange_symbol = normalize_exchange_symbol(symbol_guess)
|
exchange_symbol = normalize_exchange_symbol(symbol_guess)
|
||||||
title_prefix = f"{symbol_guess} journal {entry_id[:8]}"
|
title_prefix = f"{symbol_guess} journal {entry_id[:8]}"
|
||||||
close_ms = _local_input_datetime_to_ms(d.get("close_datetime"))
|
|
||||||
marker_payload = {
|
marker_payload = {
|
||||||
"exit_ts_ms": close_ms,
|
"entry_ts_ms": _local_input_datetime_to_ms(d.get("open_datetime")),
|
||||||
"entry_ts_ms": close_ms,
|
"exit_ts_ms": _local_input_datetime_to_ms(d.get("close_datetime")),
|
||||||
"entry_price": d.get("entry_price_hint"),
|
"entry_price": d.get("entry_price_hint"),
|
||||||
"exit_price": None,
|
"exit_price": None,
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-15
@@ -636,6 +636,15 @@ def _local_input_datetime_to_ms(dt_text):
|
|||||||
return None
|
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 _pick_marker_point(rows, target_ts_ms, target_price=None):
|
def _pick_marker_point(rows, target_ts_ms, target_price=None):
|
||||||
if not rows or target_ts_ms is None:
|
if not rows or target_ts_ms is None:
|
||||||
return None, None
|
return None, None
|
||||||
@@ -690,7 +699,7 @@ def _render_candles_subplot(rows, title, width, height, bg_rgb=(255, 255, 255),
|
|||||||
continue
|
continue
|
||||||
if idx < 0 or idx >= n:
|
if idx < 0 or idx >= n:
|
||||||
continue
|
continue
|
||||||
marker_by_idx[idx] = mp
|
marker_by_idx.setdefault(idx, []).append(mp)
|
||||||
|
|
||||||
x0 = pad_l
|
x0 = pad_l
|
||||||
for i, r in enumerate(rows):
|
for i, r in enumerate(rows):
|
||||||
@@ -717,27 +726,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)
|
draw.rectangle((left, top, left + body_w, bot), fill=(255, 255, 255), outline=edge_color, width=1)
|
||||||
else:
|
else:
|
||||||
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
||||||
if i in marker_by_idx:
|
for j, mp in enumerate(marker_by_idx.get(i, [])):
|
||||||
mp = marker_by_idx[i]
|
|
||||||
tag = str(mp.get("tag") or "")
|
tag = str(mp.get("tag") or "")
|
||||||
|
label = _marker_tag_label(tag)
|
||||||
m_price = float(mp.get("price") or r["c"])
|
m_price = float(mp.get("price") or r["c"])
|
||||||
y_m = pad_t + int((hi - m_price) / (hi - lo) * plot_h)
|
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))
|
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":
|
if tag == "ENTRY":
|
||||||
m_color = (0, 195, 95)
|
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
|
text_y = y_m - 36
|
||||||
else:
|
else:
|
||||||
m_color = (235, 65, 65)
|
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
|
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.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:
|
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:
|
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
|
x0 = x1
|
||||||
|
|
||||||
if len(marker_points or []) >= 2:
|
if len(marker_points or []) >= 2:
|
||||||
@@ -847,6 +858,7 @@ def generate_multi_timeframe_chart_png(
|
|||||||
end_ts_ms = int(marker_payload.get("exit_ts_ms") or marker_payload.get("entry_ts_ms") or 0) or None
|
end_ts_ms = int(marker_payload.get("exit_ts_ms") or marker_payload.get("entry_ts_ms") or 0) or None
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
end_ts_ms = None
|
end_ts_ms = None
|
||||||
|
default_marker_tfs = {str(t).strip().lower() for t in timeframes}
|
||||||
for tf in timeframes:
|
for tf in timeframes:
|
||||||
try:
|
try:
|
||||||
ohlcv = _fetch_ohlcv_ending_at(exchange_symbol, tf, limit, end_ts_ms)
|
ohlcv = _fetch_ohlcv_ending_at(exchange_symbol, tf, limit, end_ts_ms)
|
||||||
@@ -858,7 +870,13 @@ def generate_multi_timeframe_chart_png(
|
|||||||
title = f"{title_prefix} | {tf} x{len(rows)}"
|
title = f"{title_prefix} | {tf} x{len(rows)}"
|
||||||
points = []
|
points = []
|
||||||
tf_key = str(tf).strip().lower()
|
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:
|
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"))
|
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"))
|
exit_idx, exit_price = _pick_marker_point(rows, marker_payload.get("exit_ts_ms"), marker_payload.get("exit_price"))
|
||||||
@@ -916,7 +934,26 @@ def generate_multi_timeframe_chart_png(
|
|||||||
return fname
|
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(
|
return generate_multi_timeframe_chart_png(
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
title_prefix,
|
title_prefix,
|
||||||
@@ -925,6 +962,8 @@ def generate_order_open_chart(exchange_symbol, title_prefix, timeframes=None, li
|
|||||||
out_dir=ORDER_CHART_DIR,
|
out_dir=ORDER_CHART_DIR,
|
||||||
filename=None,
|
filename=None,
|
||||||
filename_prefix="order",
|
filename_prefix="order",
|
||||||
|
marker_payload=marker_payload,
|
||||||
|
marker_timeframes=marker_tfs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -6617,7 +6656,12 @@ def add_order():
|
|||||||
if make_order_chart and ORDER_CHART_ENABLED:
|
if make_order_chart and ORDER_CHART_ENABLED:
|
||||||
try:
|
try:
|
||||||
title_prefix = f"{symbol} {direction} #{new_order_id}"
|
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:
|
if chart_name:
|
||||||
chart_url = f"/static/images/order_charts/{chart_name}"
|
chart_url = f"/static/images/order_charts/{chart_name}"
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -7173,10 +7217,9 @@ def add_journal():
|
|||||||
symbol_guess = normalize_symbol_input(coin) or coin
|
symbol_guess = normalize_symbol_input(coin) or coin
|
||||||
exchange_symbol = normalize_exchange_symbol(symbol_guess)
|
exchange_symbol = normalize_exchange_symbol(symbol_guess)
|
||||||
title_prefix = f"{symbol_guess} journal {entry_id[:8]}"
|
title_prefix = f"{symbol_guess} journal {entry_id[:8]}"
|
||||||
close_ms = _local_input_datetime_to_ms(d.get("close_datetime"))
|
|
||||||
marker_payload = {
|
marker_payload = {
|
||||||
"exit_ts_ms": close_ms,
|
"entry_ts_ms": _local_input_datetime_to_ms(d.get("open_datetime")),
|
||||||
"entry_ts_ms": close_ms,
|
"exit_ts_ms": _local_input_datetime_to_ms(d.get("close_datetime")),
|
||||||
"entry_price": d.get("entry_price_hint"),
|
"entry_price": d.get("entry_price_hint"),
|
||||||
"exit_price": None,
|
"exit_price": None,
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-13
@@ -625,6 +625,67 @@ def _local_input_datetime_to_ms(dt_text):
|
|||||||
return None
|
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):
|
def _pick_marker_point(rows, target_ts_ms, target_price=None):
|
||||||
if not rows or target_ts_ms is None:
|
if not rows or target_ts_ms is None:
|
||||||
return None, None
|
return None, None
|
||||||
@@ -679,7 +740,7 @@ def _render_candles_subplot(rows, title, width, height, bg_rgb=(255, 255, 255),
|
|||||||
continue
|
continue
|
||||||
if idx < 0 or idx >= n:
|
if idx < 0 or idx >= n:
|
||||||
continue
|
continue
|
||||||
marker_by_idx[idx] = mp
|
marker_by_idx.setdefault(idx, []).append(mp)
|
||||||
|
|
||||||
x0 = pad_l
|
x0 = pad_l
|
||||||
for i, r in enumerate(rows):
|
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)
|
draw.rectangle((left, top, left + body_w, bot), fill=(255, 255, 255), outline=edge_color, width=1)
|
||||||
else:
|
else:
|
||||||
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
||||||
if i in marker_by_idx:
|
for j, mp in enumerate(marker_by_idx.get(i, [])):
|
||||||
mp = marker_by_idx[i]
|
|
||||||
tag = str(mp.get("tag") or "")
|
tag = str(mp.get("tag") or "")
|
||||||
|
label = _marker_tag_label(tag)
|
||||||
m_price = float(mp.get("price") or r["c"])
|
m_price = float(mp.get("price") or r["c"])
|
||||||
y_m = pad_t + int((hi - m_price) / (hi - lo) * plot_h)
|
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))
|
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":
|
if tag == "ENTRY":
|
||||||
m_color = (0, 195, 95)
|
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
|
text_y = y_m - 36
|
||||||
else:
|
else:
|
||||||
m_color = (235, 65, 65)
|
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
|
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.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:
|
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:
|
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
|
x0 = x1
|
||||||
|
|
||||||
if len(marker_points or []) >= 2:
|
if len(marker_points or []) >= 2:
|
||||||
@@ -778,16 +841,31 @@ def generate_multi_timeframe_chart_png(
|
|||||||
ensure_markets_loaded()
|
ensure_markets_loaded()
|
||||||
panels = []
|
panels = []
|
||||||
cell_w, cell_h = 980, 520
|
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:
|
for tf in timeframes:
|
||||||
try:
|
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:
|
except Exception:
|
||||||
ohlcv = []
|
ohlcv = []
|
||||||
rows = _ohlcv_to_rows(ohlcv)[-limit:]
|
rows = _ohlcv_to_rows(ohlcv)[-limit:]
|
||||||
title = f"{title_prefix} | {tf} x{len(rows)}"
|
title = f"{title_prefix} | {tf} x{len(rows)}"
|
||||||
points = []
|
points = []
|
||||||
tf_key = str(tf).strip().lower()
|
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:
|
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"))
|
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"))
|
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
|
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(
|
return generate_multi_timeframe_chart_png(
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
title_prefix,
|
title_prefix,
|
||||||
@@ -854,6 +951,8 @@ def generate_order_open_chart(exchange_symbol, title_prefix, timeframes=None, li
|
|||||||
out_dir=ORDER_CHART_DIR,
|
out_dir=ORDER_CHART_DIR,
|
||||||
filename=None,
|
filename=None,
|
||||||
filename_prefix="order",
|
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:
|
if make_order_chart and ORDER_CHART_ENABLED:
|
||||||
try:
|
try:
|
||||||
title_prefix = f"{symbol} {direction} #{new_order_id}"
|
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:
|
if chart_name:
|
||||||
chart_url = f"/static/images/order_charts/{chart_name}"
|
chart_url = f"/static/images/order_charts/{chart_name}"
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
+60
-16
@@ -540,6 +540,15 @@ def _local_input_datetime_to_ms(dt_text):
|
|||||||
return None
|
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 _pick_marker_point(rows, target_ts_ms, target_price=None):
|
def _pick_marker_point(rows, target_ts_ms, target_price=None):
|
||||||
if not rows or target_ts_ms is None:
|
if not rows or target_ts_ms is None:
|
||||||
return None, None
|
return None, None
|
||||||
@@ -594,7 +603,7 @@ def _render_candles_subplot(rows, title, width, height, bg_rgb=(255, 255, 255),
|
|||||||
continue
|
continue
|
||||||
if idx < 0 or idx >= n:
|
if idx < 0 or idx >= n:
|
||||||
continue
|
continue
|
||||||
marker_by_idx[idx] = mp
|
marker_by_idx.setdefault(idx, []).append(mp)
|
||||||
|
|
||||||
x0 = pad_l
|
x0 = pad_l
|
||||||
for i, r in enumerate(rows):
|
for i, r in enumerate(rows):
|
||||||
@@ -621,27 +630,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)
|
draw.rectangle((left, top, left + body_w, bot), fill=(255, 255, 255), outline=edge_color, width=1)
|
||||||
else:
|
else:
|
||||||
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
draw.rectangle((left, top, left + body_w, bot), fill=edge_color, outline=edge_color, width=1)
|
||||||
if i in marker_by_idx:
|
for j, mp in enumerate(marker_by_idx.get(i, [])):
|
||||||
mp = marker_by_idx[i]
|
|
||||||
tag = str(mp.get("tag") or "")
|
tag = str(mp.get("tag") or "")
|
||||||
|
label = _marker_tag_label(tag)
|
||||||
m_price = float(mp.get("price") or r["c"])
|
m_price = float(mp.get("price") or r["c"])
|
||||||
y_m = pad_t + int((hi - m_price) / (hi - lo) * plot_h)
|
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))
|
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":
|
if tag == "ENTRY":
|
||||||
m_color = (0, 195, 95)
|
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
|
text_y = y_m - 36
|
||||||
else:
|
else:
|
||||||
m_color = (235, 65, 65)
|
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
|
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.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:
|
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:
|
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
|
x0 = x1
|
||||||
|
|
||||||
if len(marker_points or []) >= 2:
|
if len(marker_points or []) >= 2:
|
||||||
@@ -750,6 +761,7 @@ def generate_multi_timeframe_chart_png(
|
|||||||
end_ts_ms = int(marker_payload.get("exit_ts_ms") or marker_payload.get("entry_ts_ms") or 0) or None
|
end_ts_ms = int(marker_payload.get("exit_ts_ms") or marker_payload.get("entry_ts_ms") or 0) or None
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
end_ts_ms = None
|
end_ts_ms = None
|
||||||
|
default_marker_tfs = {str(t).strip().lower() for t in timeframes}
|
||||||
for tf in timeframes:
|
for tf in timeframes:
|
||||||
try:
|
try:
|
||||||
ohlcv = _fetch_ohlcv_ending_at(exchange_symbol, tf, limit, end_ts_ms)
|
ohlcv = _fetch_ohlcv_ending_at(exchange_symbol, tf, limit, end_ts_ms)
|
||||||
@@ -760,8 +772,15 @@ def generate_multi_timeframe_chart_png(
|
|||||||
rows = _ohlcv_to_rows(ohlcv)[-limit:]
|
rows = _ohlcv_to_rows(ohlcv)[-limit:]
|
||||||
title = f"{title_prefix} | {tf} x{len(rows)}"
|
title = f"{title_prefix} | {tf} x{len(rows)}"
|
||||||
points = []
|
points = []
|
||||||
marker_tfs = set(marker_timeframes or [])
|
tf_key = str(tf).strip().lower()
|
||||||
if marker_payload and tf in marker_tfs:
|
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"))
|
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"))
|
exit_idx, exit_price = _pick_marker_point(rows, marker_payload.get("exit_ts_ms"), marker_payload.get("exit_price"))
|
||||||
if entry_idx is not None and entry_price is not None:
|
if entry_idx is not None and entry_price is not None:
|
||||||
@@ -818,7 +837,26 @@ def generate_multi_timeframe_chart_png(
|
|||||||
return fname
|
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(
|
return generate_multi_timeframe_chart_png(
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
title_prefix,
|
title_prefix,
|
||||||
@@ -827,6 +865,8 @@ def generate_order_open_chart(exchange_symbol, title_prefix, timeframes=None, li
|
|||||||
out_dir=ORDER_CHART_DIR,
|
out_dir=ORDER_CHART_DIR,
|
||||||
filename=None,
|
filename=None,
|
||||||
filename_prefix="order",
|
filename_prefix="order",
|
||||||
|
marker_payload=marker_payload,
|
||||||
|
marker_timeframes=marker_tfs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -4890,7 +4930,12 @@ def add_order():
|
|||||||
if make_order_chart and ORDER_CHART_ENABLED:
|
if make_order_chart and ORDER_CHART_ENABLED:
|
||||||
try:
|
try:
|
||||||
title_prefix = f"{symbol} {direction} #{new_order_id}"
|
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:
|
if chart_name:
|
||||||
chart_url = f"/static/images/order_charts/{chart_name}"
|
chart_url = f"/static/images/order_charts/{chart_name}"
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -5409,10 +5454,9 @@ def add_journal():
|
|||||||
symbol_guess = normalize_symbol_input(coin) or coin
|
symbol_guess = normalize_symbol_input(coin) or coin
|
||||||
exchange_symbol = normalize_okx_symbol(symbol_guess)
|
exchange_symbol = normalize_okx_symbol(symbol_guess)
|
||||||
title_prefix = f"{symbol_guess} journal {entry_id[:8]}"
|
title_prefix = f"{symbol_guess} journal {entry_id[:8]}"
|
||||||
close_ms = _local_input_datetime_to_ms(d.get("close_datetime"))
|
|
||||||
marker_payload = {
|
marker_payload = {
|
||||||
"exit_ts_ms": close_ms,
|
"entry_ts_ms": _local_input_datetime_to_ms(d.get("open_datetime")),
|
||||||
"entry_ts_ms": close_ms,
|
"exit_ts_ms": _local_input_datetime_to_ms(d.get("close_datetime")),
|
||||||
"entry_price": d.get("entry_price_hint"),
|
"entry_price": d.get("entry_price_hint"),
|
||||||
"exit_price": None,
|
"exit_price": None,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user