fix bug
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -111,7 +111,7 @@ def RunOnce(run: Runtime, signals:list[SignalItem]) -> None:
|
|||||||
log.info("[运行] 非交易时间,跳过本轮")
|
log.info("[运行] 非交易时间,跳过本轮")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("=" * 40 + f" RunOnce {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} " +"=" * 40)
|
print("=" * 40 + f" Ticker {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} " +"=" * 40)
|
||||||
|
|
||||||
started_at = time.monotonic()
|
started_at = time.monotonic()
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ def RunOnce(run: Runtime, signals:list[SignalItem]) -> None:
|
|||||||
try:
|
try:
|
||||||
run.orders.refresh(run.client)
|
run.orders.refresh(run.client)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("[订单] 刷新订单失败")
|
log.exception("[Order] 刷新订单失败")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ def RunOnce(run: Runtime, signals:list[SignalItem]) -> None:
|
|||||||
return
|
return
|
||||||
allow_open_by_cash = assets.available >= assets.total * run.account_cfg.min_cash_ratio
|
allow_open_by_cash = assets.available >= assets.total * run.account_cfg.min_cash_ratio
|
||||||
if not allow_open_by_cash:
|
if not allow_open_by_cash:
|
||||||
log.info("[开仓] 禁止开仓:可用资金不足,可用=%.2f,总资产=%.2f", assets.available, assets.total)
|
log.info("[Status] 禁止开仓:可用资金不足,可用=%.2f,总资产=%.2f", assets.available, assets.total)
|
||||||
|
|
||||||
# 3. 获取大盘状态,只有大盘信号允许时才执行开仓。
|
# 3. 获取大盘状态,只有大盘信号允许时才执行开仓。
|
||||||
market_ok = market_allow_open()
|
market_ok = market_allow_open()
|
||||||
@@ -140,7 +140,7 @@ def RunOnce(run: Runtime, signals:list[SignalItem]) -> None:
|
|||||||
try:
|
try:
|
||||||
position_codes, positions = run.client.positions()
|
position_codes, positions = run.client.positions()
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("[持仓] 获取持仓失败")
|
log.exception("[Position] 获取持仓失败")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 5. 验证有效开仓信号:排除已有持仓和未决订单。
|
# 5. 验证有效开仓信号:排除已有持仓和未决订单。
|
||||||
|
|||||||
@@ -14,41 +14,41 @@ import logging as log
|
|||||||
|
|
||||||
def open_signal(run:Runtime, ticks, open_signals) -> None:
|
def open_signal(run:Runtime, ticks, open_signals) -> None:
|
||||||
"""逐个验证开仓信号并提交买入委托。"""
|
"""逐个验证开仓信号并提交买入委托。"""
|
||||||
log.info("[开仓] 信号总数:%d", len(open_signals))
|
log.info("[Open] 信号总数:%d", len(open_signals))
|
||||||
for item in open_signals:
|
for item in open_signals:
|
||||||
# 1. 验证信号配置允许开仓的时间区间。
|
# 1. 验证信号配置允许开仓的时间区间。
|
||||||
signal_config = run.global_cfg.signals.get(item.signal_key)
|
signal_config = run.global_cfg.signals.get(item.signal_key)
|
||||||
if signal_config is None or not check_timezone(signal_config.timezone):
|
if signal_config is None or not check_timezone(signal_config.timezone):
|
||||||
log.info("[开仓] %s 信号=%s,跳过:不在信号时间段(%s)", item.code, item.signal_key,signal_config.timezone)
|
log.info("[Open] %s 信号=%s,跳过:不在信号时间段(%s)", item.code, item.signal_key,signal_config.timezone)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 2. 检查该证券是否已有买入委托锁,防止重复下单。
|
# 2. 检查该证券是否已有买入委托锁,防止重复下单。
|
||||||
if run.orders.busy(item.code,"BUY"):
|
if run.orders.busy(item.code,"BUY"):
|
||||||
log.info("[开仓] %s 信号=%s,跳过:买入委托处理中", item.code, item.signal_key)
|
log.info("[Open] %s 信号=%s,跳过:买入委托处理中", item.code, item.signal_key)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 3. 验证行情和最新价格是否有效。
|
# 3. 验证行情和最新价格是否有效。
|
||||||
tick = ticks.get(item.code)
|
tick = ticks.get(item.code)
|
||||||
price = tick.last_price if tick is not None else 0
|
price = tick.last_price if tick is not None else 0
|
||||||
if price <= 0:
|
if price <= 0:
|
||||||
log.info("[开仓] %s 信号=%s,跳过:价格无效", item.code, item.signal_key)
|
log.info("[Open] %s 信号=%s,跳过:价格无效", item.code, item.signal_key)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 5. 根据单笔买入金额计算整手开仓数量。
|
# 5. 根据单笔买入金额计算整手开仓数量。
|
||||||
volume = calc_buy_volume(price, run.account_cfg.buy_value)
|
volume = calc_buy_volume(price, run.account_cfg.buy_value)
|
||||||
if volume <= 0:
|
if volume <= 0:
|
||||||
log.info("[开仓] %s 信号=%s,跳过:数量无效", item.code, item.signal_key)
|
log.info("[Open] %s 信号=%s,跳过:数量无效", item.code, item.signal_key)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 当前价高于昨收价可开仓
|
# 当前价高于昨收价可开仓
|
||||||
if signal_config.gt_last_price_is_open and item.last_close>0 and price>item.last_close:
|
if signal_config.gt_last_price_is_open and item.last_close>0 and price>item.last_close:
|
||||||
try:
|
try:
|
||||||
do_open(run,item.code,volume,item.signal_key)
|
do_open(run,item.code,volume,item.signal_key)
|
||||||
log.info("[开仓] %s 信号=%s,买入=%d股,原因=现价高于昨收", item.code, item.signal_key, volume)
|
log.info("[Open] %s 信号=%s,买入=%d股,原因=现价高于昨收", item.code, item.signal_key, volume)
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
log.info("[开仓] %s 信号=%s,买入=%d股失败:%s", item.code, item.signal_key, volume, exc)
|
log.info("[Open] %s 信号=%s,买入=%d股失败:%s", item.code, item.signal_key, volume, exc)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("[开仓] %s 信号=%s,买入=%d股异常", item.code, item.signal_key, volume)
|
log.exception("[Open] %s 信号=%s,买入=%d股异常", item.code, item.signal_key, volume)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 4. 等待价格从观察低点反弹,防止直接接下跌中的“飞刀”。
|
# 4. 等待价格从观察低点反弹,防止直接接下跌中的“飞刀”。
|
||||||
@@ -57,11 +57,11 @@ def open_signal(run:Runtime, ticks, open_signals) -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
do_open(run,item.code,volume,item.signal_key)
|
do_open(run,item.code,volume,item.signal_key)
|
||||||
log.info("[开仓] %s 信号=%s,买入=%d股,原因=反弹已确认", item.code, item.signal_key, volume)
|
log.info("[Open] %s 信号=%s,买入=%d股,原因=反弹已确认", item.code, item.signal_key, volume)
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
log.warning("[开仓] %s 信号=%s,买入=%d股失败:%s", item.code, item.signal_key, volume, exc)
|
log.warning("[Open] %s 信号=%s,买入=%d股失败:%s", item.code, item.signal_key, volume, exc)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("[开仓] %s 信号=%s,买入=%d股异常", item.code, item.signal_key, volume)
|
log.exception("[Open] %s 信号=%s,买入=%d股异常", item.code, item.signal_key, volume)
|
||||||
|
|
||||||
|
|
||||||
def do_open(run:Runtime,code:str,volume:int,signal_key:str)->None:
|
def do_open(run:Runtime,code:str,volume:int,signal_key:str)->None:
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class OrderBook:
|
|||||||
):
|
):
|
||||||
client.cancel_by_id(item.id)
|
client.cancel_by_id(item.id)
|
||||||
canceled += 1
|
canceled += 1
|
||||||
log.info("[订单] 超时撤单,代码=%s,方向=%s,柜台订单=%s", item.code, item.side, item.id)
|
log.info("[Order] 超时撤单,代码=%s,方向=%s,柜台订单=%s", item.code, item.side, item.id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 缓存本次有效订单
|
# 缓存本次有效订单
|
||||||
@@ -90,7 +90,7 @@ class OrderBook:
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.data = data
|
self.data = data
|
||||||
self.lock = lock
|
self.lock = lock
|
||||||
log.info("[订单] 刷新完成,跟踪=%d,处理中=%d,撤销=%d", len(data), len(lock), canceled)
|
log.info("[Order] 刷新完成,跟踪=%d,处理中=%d,撤销=%d", len(data), len(lock), canceled)
|
||||||
|
|
||||||
def place(self, request: PlaceOrderRequest) -> bool:
|
def place(self, request: PlaceOrderRequest) -> bool:
|
||||||
"""按最新价提交委托,并立即写入本地方向锁。"""
|
"""按最新价提交委托,并立即写入本地方向锁。"""
|
||||||
@@ -103,14 +103,14 @@ class OrderBook:
|
|||||||
request.order_id,
|
request.order_id,
|
||||||
)
|
)
|
||||||
except APIError as exc:
|
except APIError as exc:
|
||||||
log.exception("[订单] 下单失败,代码=%s,本地订单=%s,HTTP状态=%d,错误=%s", request.code, request.order_id, exc.status_code, exc.message or str(exc))
|
log.exception("[Order] 下单失败,代码=%s,本地订单=%s,HTTP状态=%d,错误=%s", request.code, request.order_id, exc.status_code, exc.message or str(exc))
|
||||||
return False
|
return False
|
||||||
if not isinstance(result, dict):
|
if not isinstance(result, dict):
|
||||||
log.warning("[订单] 下单失败,代码=%s,本地订单=%s,原因=响应格式无效", request.code, request.order_id)
|
log.warning("[Order] 下单失败,代码=%s,本地订单=%s,原因=响应格式无效", request.code, request.order_id)
|
||||||
return False
|
return False
|
||||||
order_ref = str(result.get("order_ref") or "").strip().lower()
|
order_ref = str(result.get("order_ref") or "").strip().lower()
|
||||||
if result.get("status") != "success" or order_ref in {"", "unknown", "none"}:
|
if result.get("status") != "success" or order_ref in {"", "unknown", "none"}:
|
||||||
log.warning("[订单] 下单被拒绝,代码=%s,本地订单=%s,状态=%s,柜台订单=%s", request.code, request.order_id, result.get("status"), order_ref)
|
log.warning("[Order] 下单被拒绝,代码=%s,本地订单=%s,状态=%s,柜台订单=%s", request.code, request.order_id, result.get("status"), order_ref)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
side = ORDER_SIDE_BY_OFFSET.get(str(request.op), "")
|
side = ORDER_SIDE_BY_OFFSET.get(str(request.op), "")
|
||||||
@@ -128,5 +128,5 @@ class OrderBook:
|
|||||||
key = f"{side}-{request.code}"
|
key = f"{side}-{request.code}"
|
||||||
self.data.append(pending)
|
self.data.append(pending)
|
||||||
self.lock[key] = pending.created_at.timestamp()
|
self.lock[key] = pending.created_at.timestamp()
|
||||||
log.info("[订单] 下单已受理,代码=%s,方向=%s,数量=%d,本地订单=%s,柜台订单=%s", request.code, side, request.volume, request.order_id, order_ref)
|
log.info("[Order] 下单已受理,代码=%s,方向=%s,数量=%d,本地订单=%s,柜台订单=%s", request.code, side, request.volume, request.order_id, order_ref)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def manage_positions(
|
|||||||
code = position.stock_code
|
code = position.stock_code
|
||||||
tick = ticks.get(code)
|
tick = ticks.get(code)
|
||||||
if code in runtime.account_cfg.excluded_codes:
|
if code in runtime.account_cfg.excluded_codes:
|
||||||
log.info("[持仓] 代码=%s,名称=%s,止盈=跳过,补仓=跳过,原因=已配置为排除股票", code, position.stock_name)
|
log.info("[Position] %s %s 止盈=跳过,补仓=跳过,原因=已配置为排除股票", code, position.stock_name)
|
||||||
continue
|
continue
|
||||||
if (
|
if (
|
||||||
not code
|
not code
|
||||||
@@ -56,7 +56,7 @@ def manage_positions(
|
|||||||
or tick is None
|
or tick is None
|
||||||
or tick.last_price <= 0
|
or tick.last_price <= 0
|
||||||
):
|
):
|
||||||
log.warning("[持仓] 代码=%s,名称=%s,止盈=跳过,补仓=跳过,原因=持仓或行情数据无效", code or "未知", position.stock_name)
|
log.warning("[Position] %s %s 止盈=跳过,补仓=跳过,原因=持仓或行情数据无效", code or "未知", position.stock_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pnl_rate = round(
|
pnl_rate = round(
|
||||||
@@ -87,7 +87,7 @@ def manage_positions(
|
|||||||
loss_add_action = "大盘信号不允许"
|
loss_add_action = "大盘信号不允许"
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
"[持仓] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s",
|
"[Position] %s %s 盈亏=%.2f%%,止盈=%s,补仓=%s",
|
||||||
code, position.stock_name, pnl_rate, profit_action, loss_add_action,
|
code, position.stock_name, pnl_rate, profit_action, loss_add_action,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class DipWatch:
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
"""更新观察价格;达到反弹阈值时返回 ``True``。"""
|
"""更新观察价格;达到反弹阈值时返回 ``True``。"""
|
||||||
if price <= 0:
|
if price <= 0:
|
||||||
log.warning("[%s观察] %s 价格无效:%.2f", tag, code, price)
|
log.warning("[%s Watch] %s 价格无效:%.2f", tag, code, price)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
current = now or datetime.now()
|
current = now or datetime.now()
|
||||||
@@ -42,24 +42,23 @@ class DipWatch:
|
|||||||
if watch is None:
|
if watch is None:
|
||||||
self._start(code, price, current)
|
self._start(code, price, current)
|
||||||
log.info(
|
log.info(
|
||||||
"[%s观察] %s 开始观察,收盘价=%.2f,反弹阈值=%.2f%%",
|
"[%s Watch] %s 开始观察,收盘价=%.2f",
|
||||||
tag,
|
tag,
|
||||||
code,
|
code,
|
||||||
price,
|
price,
|
||||||
self.rebound_threshold,
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if current >= watch.expires_at:
|
if current >= watch.expires_at:
|
||||||
self._start(code, price, current)
|
self._start(code, price, current)
|
||||||
log.info("[%s观察] %s 观察已过期,重新观察,收盘价=%.2f", tag, code, price)
|
log.info("[%sWatch] %s 观察已过期,重新观察,收盘价=%.2f", tag, code, price)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if price < watch.last_close:
|
if price < watch.last_close:
|
||||||
old_price = watch.last_close
|
old_price = watch.last_close
|
||||||
self._start(code, price, current)
|
self._start(code, price, current)
|
||||||
log.info(
|
log.info(
|
||||||
"[%s观察] %s 刷新低点,原收盘价=%.2f,新收盘价=%.2f",
|
"[%s Watch] %s 刷新低点,原收盘价=%.2f,新收盘价=%.2f",
|
||||||
tag,
|
tag,
|
||||||
code,
|
code,
|
||||||
old_price,
|
old_price,
|
||||||
@@ -70,7 +69,7 @@ class DipWatch:
|
|||||||
rebound = (price - watch.last_close) / watch.last_close * 100
|
rebound = (price - watch.last_close) / watch.last_close * 100
|
||||||
if rebound < self.rebound_threshold:
|
if rebound < self.rebound_threshold:
|
||||||
log.debug(
|
log.debug(
|
||||||
"[%s观察] %s 等待反弹,收盘价=%.2f,现价=%.2f,反弹=%.2f%%,阈值=%.2f%%",
|
"[%s Watch] %s 等待反弹,收盘价=%.2f,现价=%.2f,反弹=%.2f%%,阈值=%.2f%%",
|
||||||
tag,
|
tag,
|
||||||
code,
|
code,
|
||||||
watch.last_close,
|
watch.last_close,
|
||||||
@@ -82,7 +81,7 @@ class DipWatch:
|
|||||||
|
|
||||||
del self.data[code]
|
del self.data[code]
|
||||||
log.info(
|
log.info(
|
||||||
"[%s观察] %s 反弹触发,收盘价=%.2f,现价=%.2f,反弹=%.2f%%",
|
"[%s Watch] %s 反弹触发,收盘价=%.2f,现价=%.2f,反弹=%.2f%%",
|
||||||
tag,
|
tag,
|
||||||
code,
|
code,
|
||||||
watch.last_close,
|
watch.last_close,
|
||||||
@@ -96,7 +95,7 @@ class DipWatch:
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
removed = self.data.pop(code, None)
|
removed = self.data.pop(code, None)
|
||||||
if removed is not None:
|
if removed is not None:
|
||||||
log.info("[价格观察] %s 已清除观察状态", code)
|
log.info("[Watch] %s 已清除观察状态", code)
|
||||||
|
|
||||||
def _start(self, code: str, price: float, now: datetime) -> None:
|
def _start(self, code: str, price: float, now: datetime) -> None:
|
||||||
self.data[code] = _Entry(
|
self.data[code] = _Entry(
|
||||||
|
|||||||
Reference in New Issue
Block a user