Show day OHLC, session label, clarify auto-lot for small balance.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+100
-7
@@ -9,10 +9,12 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from tkinter import messagebox, simpledialog, ttk
|
from tkinter import messagebox, simpledialog, ttk
|
||||||
from typing import Any, Optional, Tuple, Union
|
from typing import Any, Optional, Tuple, Union
|
||||||
|
|
||||||
|
BJT = timezone(timedelta(hours=8))
|
||||||
|
|
||||||
# 顶层导入,避免打包 exe 漏依赖
|
# 顶层导入,避免打包 exe 漏依赖
|
||||||
import numpy # noqa: F401
|
import numpy # noqa: F401
|
||||||
import MetaTrader5 as mt5
|
import MetaTrader5 as mt5
|
||||||
@@ -99,6 +101,7 @@ def _init_mt5_constants() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def suggest_lot_by_balance(balance: float) -> float:
|
def suggest_lot_by_balance(balance: float) -> float:
|
||||||
|
"""按余额档位手数:≥10000→0.10;1000~10000→0.05;<1000→0.01(点按钮即可)。"""
|
||||||
if balance >= BALANCE_HIGH:
|
if balance >= BALANCE_HIGH:
|
||||||
return LOT_HIGH
|
return LOT_HIGH
|
||||||
if balance < BALANCE_LOW:
|
if balance < BALANCE_LOW:
|
||||||
@@ -106,6 +109,29 @@ def suggest_lot_by_balance(balance: float) -> float:
|
|||||||
return LOT_MID
|
return LOT_MID
|
||||||
|
|
||||||
|
|
||||||
|
def current_session_label(now: Optional[datetime] = None) -> str:
|
||||||
|
"""北京时间盘段:亚盘/欧盘/美盘(可重叠显示)。"""
|
||||||
|
dt = now or datetime.now(BJT)
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
dt = dt.replace(tzinfo=BJT)
|
||||||
|
else:
|
||||||
|
dt = dt.astimezone(BJT)
|
||||||
|
h = dt.hour + dt.minute / 60.0
|
||||||
|
asia = 8.0 <= h < 16.0
|
||||||
|
euro = 15.0 <= h < 24.0
|
||||||
|
us = h >= 20.0 or h < 5.0
|
||||||
|
parts = []
|
||||||
|
if asia:
|
||||||
|
parts.append("亚盘")
|
||||||
|
if euro:
|
||||||
|
parts.append("欧盘")
|
||||||
|
if us:
|
||||||
|
parts.append("美盘")
|
||||||
|
if not parts:
|
||||||
|
return "过渡时段"
|
||||||
|
return "+".join(parts)
|
||||||
|
|
||||||
|
|
||||||
class TradeError(Exception):
|
class TradeError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -153,6 +179,31 @@ class Mt5Bridge:
|
|||||||
raise TradeError(f"无法读取品种信息 {symbol}")
|
raise TradeError(f"无法读取品种信息 {symbol}")
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
def day_ohlc(self, symbol: str) -> dict[str, Any]:
|
||||||
|
"""今日(服务器日线)开高低;用最新 tick 实时修正最高/最低。"""
|
||||||
|
self.ensure_symbol(symbol)
|
||||||
|
rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 1)
|
||||||
|
if rates is None or len(rates) < 1:
|
||||||
|
raise TradeError("无法读取今日日线,请确认品种有 D1 数据")
|
||||||
|
bar = rates[0]
|
||||||
|
open_px = float(bar["open"])
|
||||||
|
high = float(bar["high"])
|
||||||
|
low = float(bar["low"])
|
||||||
|
open_ts = int(bar["time"])
|
||||||
|
tick = mt5.symbol_info_tick(symbol)
|
||||||
|
if tick is not None:
|
||||||
|
bid = float(tick.bid)
|
||||||
|
ask = float(tick.ask)
|
||||||
|
high = max(high, bid, ask)
|
||||||
|
low = min(low, bid, ask)
|
||||||
|
return {
|
||||||
|
"open": open_px,
|
||||||
|
"high": high,
|
||||||
|
"low": low,
|
||||||
|
"open_time": datetime.fromtimestamp(open_ts),
|
||||||
|
"open_ts": open_ts,
|
||||||
|
}
|
||||||
|
|
||||||
def atr(self, symbol: str, period: int = ATR_PERIOD) -> float:
|
def atr(self, symbol: str, period: int = ATR_PERIOD) -> float:
|
||||||
self.ensure_symbol(symbol)
|
self.ensure_symbol(symbol)
|
||||||
rates = mt5.copy_rates_from_pos(symbol, ATR_TIMEFRAME, 1, period + 1)
|
rates = mt5.copy_rates_from_pos(symbol, ATR_TIMEFRAME, 1, period + 1)
|
||||||
@@ -605,8 +656,8 @@ class OrderApp(tk.Tk):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
_init_mt5_constants()
|
_init_mt5_constants()
|
||||||
self.title(APP_TITLE)
|
self.title(APP_TITLE)
|
||||||
self.geometry("1080x900")
|
self.geometry("1080x940")
|
||||||
self.minsize(980, 820)
|
self.minsize(980, 860)
|
||||||
self.configure(bg="#1e1e1e")
|
self.configure(bg="#1e1e1e")
|
||||||
try:
|
try:
|
||||||
self.tk.call("tk", "scaling", 1.45)
|
self.tk.call("tk", "scaling", 1.45)
|
||||||
@@ -645,10 +696,29 @@ class OrderApp(tk.Tk):
|
|||||||
self.lbl_account = ttk.Label(head, text="余额 --", font=("Microsoft YaHei UI", 13, "bold"))
|
self.lbl_account = ttk.Label(head, text="余额 --", font=("Microsoft YaHei UI", 13, "bold"))
|
||||||
self.lbl_account.pack(side=tk.LEFT, padx=12)
|
self.lbl_account.pack(side=tk.LEFT, padx=12)
|
||||||
|
|
||||||
self.lbl_quote = ttk.Label(root, text="Bid -- Ask --", font=("Microsoft YaHei UI", 14, "bold"))
|
quote_row = ttk.Frame(root)
|
||||||
self.lbl_quote.pack(anchor="w", pady=(10, 0))
|
quote_row.pack(fill=tk.X, pady=(10, 0))
|
||||||
|
self.lbl_quote = ttk.Label(quote_row, text="Bid -- Ask --", font=("Microsoft YaHei UI", 14, "bold"))
|
||||||
|
self.lbl_quote.pack(side=tk.LEFT)
|
||||||
|
self.lbl_session = tk.Label(
|
||||||
|
quote_row,
|
||||||
|
text="时段 --",
|
||||||
|
bg="#2a3a55",
|
||||||
|
fg="#7ec8ff",
|
||||||
|
font=("Microsoft YaHei UI", 12, "bold"),
|
||||||
|
padx=10,
|
||||||
|
pady=2,
|
||||||
|
)
|
||||||
|
self.lbl_session.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
self.lbl_info = ttk.Label(root, text="点差 -- ATR -- SL$ -- TP$ -- 持仓 --", font=("Microsoft YaHei UI", 12))
|
self.lbl_info = ttk.Label(root, text="点差 -- ATR -- SL$ -- TP$ -- 持仓 --", font=("Microsoft YaHei UI", 12))
|
||||||
self.lbl_info.pack(anchor="w", pady=(4, 2))
|
self.lbl_info.pack(anchor="w", pady=(4, 2))
|
||||||
|
self.lbl_day_ohlc = ttk.Label(
|
||||||
|
root,
|
||||||
|
text="开盘 -- | 今开 -- | 最高 -- | 最低 --",
|
||||||
|
font=("Microsoft YaHei UI", 12),
|
||||||
|
)
|
||||||
|
self.lbl_day_ohlc.pack(anchor="w", pady=(2, 2))
|
||||||
today_row = ttk.Frame(root)
|
today_row = ttk.Frame(root)
|
||||||
today_row.pack(fill=tk.X, pady=(0, 12))
|
today_row.pack(fill=tk.X, pady=(0, 12))
|
||||||
self.lbl_today_base = tk.Label(
|
self.lbl_today_base = tk.Label(
|
||||||
@@ -717,7 +787,11 @@ class OrderApp(tk.Tk):
|
|||||||
self._mk_btn(opt, "按资金手数", "#3a5a80", self.on_auto_lot, h=34, w=110).pack(side=tk.LEFT, padx=10)
|
self._mk_btn(opt, "按资金手数", "#3a5a80", self.on_auto_lot, h=34, w=110).pack(side=tk.LEFT, padx=10)
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
opt,
|
opt,
|
||||||
text=f"同向加仓上限 {MAX_SIDE_LOTS:.2f} 手 · 最多同时 {MAX_OPEN_POSITIONS} 笔",
|
text=(
|
||||||
|
f"点一下按余额改手数:≥{BALANCE_HIGH:.0f}→{LOT_HIGH:.2f},"
|
||||||
|
f"{BALANCE_LOW:.0f}~→{LOT_MID:.2f},<{BALANCE_LOW:.0f}→{LOT_LOW:.2f};"
|
||||||
|
f"同向≤{MAX_SIDE_LOTS:.2f} · 最多{MAX_OPEN_POSITIONS}笔"
|
||||||
|
),
|
||||||
style="Status.TLabel",
|
style="Status.TLabel",
|
||||||
).pack(side=tk.LEFT)
|
).pack(side=tk.LEFT)
|
||||||
|
|
||||||
@@ -1160,12 +1234,19 @@ class OrderApp(tk.Tk):
|
|||||||
self.refresh_quotes()
|
self.refresh_quotes()
|
||||||
|
|
||||||
def on_auto_lot(self) -> None:
|
def on_auto_lot(self) -> None:
|
||||||
|
"""任意资金都可点:小资金(<1000)会设为 0.01 手。"""
|
||||||
try:
|
try:
|
||||||
self.bridge.connect()
|
self.bridge.connect()
|
||||||
bal = self.bridge.account_balance()
|
bal = self.bridge.account_balance()
|
||||||
lot = suggest_lot_by_balance(bal)
|
lot = suggest_lot_by_balance(bal)
|
||||||
self.var_lot.set(f"{lot:.2f}")
|
self.var_lot.set(f"{lot:.2f}")
|
||||||
self.set_status(f"按资金设置手数:余额 {bal:.2f} → {lot:.2f}", ok=True)
|
if bal < BALANCE_LOW:
|
||||||
|
tip = f"小资金档:余额 {bal:.2f} → 手数 {lot:.2f}"
|
||||||
|
elif bal >= BALANCE_HIGH:
|
||||||
|
tip = f"大资金档:余额 {bal:.2f} → 手数 {lot:.2f}"
|
||||||
|
else:
|
||||||
|
tip = f"中资金档:余额 {bal:.2f} → 手数 {lot:.2f}"
|
||||||
|
self.set_status(tip, ok=True)
|
||||||
except TradeError as exc:
|
except TradeError as exc:
|
||||||
self.set_status(str(exc), ok=False)
|
self.set_status(str(exc), ok=False)
|
||||||
|
|
||||||
@@ -1231,12 +1312,24 @@ class OrderApp(tk.Tk):
|
|||||||
self.lbl_account.configure(text="余额 --")
|
self.lbl_account.configure(text="余额 --")
|
||||||
|
|
||||||
self.lbl_quote.configure(text=f"Bid {bid:.{digits}f} Ask {ask:.{digits}f}")
|
self.lbl_quote.configure(text=f"Bid {bid:.{digits}f} Ask {ask:.{digits}f}")
|
||||||
|
self.lbl_session.configure(text=f"时段 {current_session_label()}")
|
||||||
self.lbl_info.configure(
|
self.lbl_info.configure(
|
||||||
text=(
|
text=(
|
||||||
f"点差 {spread:.{digits}f} ATRM15 {atr_txt} "
|
f"点差 {spread:.{digits}f} ATRM15 {atr_txt} "
|
||||||
f"SL${sl_txt} TP${tp_txt} 持仓 {pos} 浮盈 {profit:.2f}"
|
f"SL${sl_txt} TP${tp_txt} 持仓 {pos} 浮盈 {profit:.2f}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
day = self.bridge.day_ohlc(symbol)
|
||||||
|
ot = day["open_time"].strftime("%H:%M")
|
||||||
|
self.lbl_day_ohlc.configure(
|
||||||
|
text=(
|
||||||
|
f"开盘 {ot} | 今开 {day['open']:.{digits}f} | "
|
||||||
|
f"最高 {day['high']:.{digits}f} | 最低 {day['low']:.{digits}f}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
self.lbl_day_ohlc.configure(text="开盘 -- | 今开 -- | 最高 -- | 最低 --")
|
||||||
try:
|
try:
|
||||||
self._refresh_today_stats(symbol)
|
self._refresh_today_stats(symbol)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user