fix bug
This commit is contained in:
@@ -15,7 +15,7 @@ IPO_SESSIONS = ((time(9, 30), time(11, 30)), (time(13, 0), time(15, 0)))
|
||||
|
||||
|
||||
def AutoBuyIpo() -> None:
|
||||
"""安全执行一次新股申购,返回成功提交的证券数量。"""
|
||||
"""安全执行一次新股申购。"""
|
||||
if not config.account_config.enable_auto_ipo:
|
||||
logging.info("[IPO] 自动申购未启用")
|
||||
return
|
||||
|
||||
@@ -51,65 +51,72 @@ def StartTrend() -> None:
|
||||
config.global_config.qmt_token,
|
||||
config.HTTP_TIMEOUT,
|
||||
)
|
||||
portfolio = client.portfolio()
|
||||
assets = portfolio.assets
|
||||
positions = list(portfolio.positions.values())
|
||||
order_book = OrderBook()
|
||||
order_book.refresh(client, portfolio.orders)
|
||||
executor = None
|
||||
try:
|
||||
portfolio = client.portfolio()
|
||||
assets = portfolio.assets
|
||||
positions = list(portfolio.positions.values())
|
||||
order_book = OrderBook()
|
||||
order_book.refresh(client, portfolio.orders)
|
||||
|
||||
storeState = State.for_strategy(
|
||||
config.global_config.qmt_data_dir,
|
||||
config.account_config.strategy,
|
||||
config.account_config.account_id,
|
||||
)
|
||||
storeState.reconcile(positions, order_book.data)
|
||||
storeState = State.for_strategy(
|
||||
config.global_config.qmt_data_dir,
|
||||
config.account_config.strategy,
|
||||
config.account_config.account_id,
|
||||
)
|
||||
storeState.reconcile(positions, order_book.data)
|
||||
|
||||
# 获取本策略的信号开仓数据
|
||||
signals = init_signals(
|
||||
config.global_config,
|
||||
config.account_config.signal_allow,
|
||||
)
|
||||
log.info("[启动] 趋势策略已启动,账户=%s,信号=%d,持仓=%d", config.account_config.account_id, len(signals), len(positions))
|
||||
run = Runtime(
|
||||
client=client,
|
||||
global_cfg=config.global_config,
|
||||
account_cfg=config.account_config,
|
||||
state=storeState,
|
||||
orders=order_book,
|
||||
open_watch=DipWatch(),
|
||||
add_watch=DipWatch(),
|
||||
profit_tracker=GridTrailingTracker(config.account_config.grid_step_pct),
|
||||
executor=ThreadPoolExecutor(max_workers=3, thread_name_prefix="trend"),
|
||||
)
|
||||
# 获取本策略的信号开仓数据
|
||||
signals = init_signals(
|
||||
config.global_config,
|
||||
config.account_config.signal_allow,
|
||||
)
|
||||
log.info("[启动] 趋势策略已启动,账户=%s,信号=%d,持仓=%d", config.account_config.account_id, len(signals), len(positions))
|
||||
executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="trend")
|
||||
run = Runtime(
|
||||
client=client,
|
||||
global_cfg=config.global_config,
|
||||
account_cfg=config.account_config,
|
||||
state=storeState,
|
||||
orders=order_book,
|
||||
open_watch=DipWatch(),
|
||||
add_watch=DipWatch(),
|
||||
profit_tracker=GridTrailingTracker(config.account_config.grid_step_pct),
|
||||
executor=executor,
|
||||
)
|
||||
|
||||
Overview(assets, positions, config.account_config)
|
||||
Overview(assets, positions, config.account_config)
|
||||
|
||||
DEFAULT_TICK_INTERVAL = 30
|
||||
while True:
|
||||
lt = time.localtime()
|
||||
if (lt.tm_hour, lt.tm_min, lt.tm_sec) >= (15, 0, 0):
|
||||
log.info("[Trend] 已到 15:00,结束趋势策略")
|
||||
run.client.close()
|
||||
run.executor.shutdown()
|
||||
return
|
||||
current_sec = lt.tm_sec
|
||||
|
||||
# 计算距离下一个目标时间点(0秒或30秒)的等待时间
|
||||
if current_sec < DEFAULT_TICK_INTERVAL:
|
||||
wait_seconds = DEFAULT_TICK_INTERVAL - current_sec
|
||||
elif current_sec < 60:
|
||||
wait_seconds = 60 - current_sec
|
||||
else:
|
||||
wait_seconds = DEFAULT_TICK_INTERVAL
|
||||
|
||||
# 等待到目标时间点
|
||||
time.sleep(wait_seconds)
|
||||
|
||||
# 单轮失败不能杀死唯一的交易定时线程。
|
||||
DEFAULT_TICK_INTERVAL = 30
|
||||
while True:
|
||||
lt = time.localtime()
|
||||
if (lt.tm_hour, lt.tm_min, lt.tm_sec) >= (15, 0, 0):
|
||||
log.info("[Trend] 已到 15:00,结束趋势策略")
|
||||
return
|
||||
current_sec = lt.tm_sec
|
||||
|
||||
# 计算距离下一个目标时间点(0秒或30秒)的等待时间
|
||||
if current_sec < DEFAULT_TICK_INTERVAL:
|
||||
wait_seconds = DEFAULT_TICK_INTERVAL - current_sec
|
||||
elif current_sec < 60:
|
||||
wait_seconds = 60 - current_sec
|
||||
else:
|
||||
wait_seconds = DEFAULT_TICK_INTERVAL
|
||||
|
||||
# 等待到目标时间点
|
||||
time.sleep(wait_seconds)
|
||||
|
||||
# 单轮失败不能杀死唯一的交易定时线程。
|
||||
try:
|
||||
RunOnce(run, signals)
|
||||
except Exception as e:
|
||||
log.error(f"[Trend] 本 tick 执行失败,下一 tick 继续: {e}", exc_info=True)
|
||||
finally:
|
||||
try:
|
||||
RunOnce(run, signals)
|
||||
except Exception as e:
|
||||
log.error(f"[Trend] 本 tick 执行失败,下一 tick 继续: {e}", exc_info=True)
|
||||
if executor is not None:
|
||||
executor.shutdown(wait=True)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def RunOnce(run: Runtime, signals:list[SignalItem]) -> None:
|
||||
|
||||
@@ -15,6 +15,9 @@ import logging as log
|
||||
def open_signal(run:Runtime, ticks, open_signals) -> None:
|
||||
"""逐个验证开仓信号并提交买入委托。"""
|
||||
for item in open_signals:
|
||||
if item.code in run.account_cfg.excluded_codes:
|
||||
log.info("[Open] %s 信号=%s,跳过:已配置为排除股票", item.code, item.signal_key)
|
||||
continue
|
||||
# 1. 验证信号配置允许开仓的时间区间。
|
||||
signal_config = run.global_cfg.signals.get(item.signal_key)
|
||||
if signal_config is None:
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import datetime, timedelta
|
||||
from threading import Lock
|
||||
from typing import Any
|
||||
from cachelib import SimpleCache
|
||||
import httpx
|
||||
|
||||
from sdk import APIError, ORDER_SIDE_BY_OFFSET, Client, OrderItem
|
||||
|
||||
@@ -116,6 +117,10 @@ class OrderBook:
|
||||
except APIError as exc:
|
||||
log.exception("[Order] 下单失败,代码=%s,本地订单=%s,HTTP状态=%d,错误=%s", request.code, request.order_id, exc.status_code, exc.message or str(exc))
|
||||
return False
|
||||
except (httpx.RequestError, ValueError):
|
||||
# 响应异常不能证明柜台未受理,保留缓存防重,不自动重试。
|
||||
log.exception("[Order] 下单请求或响应异常,代码=%s,本地订单=%s", request.code, request.order_id)
|
||||
return False
|
||||
if not isinstance(result, dict):
|
||||
log.warning("[Order] 下单失败,代码=%s,本地订单=%s,原因=响应格式无效", request.code, request.order_id)
|
||||
return False
|
||||
|
||||
@@ -42,58 +42,61 @@ def manage_positions(
|
||||
remaining_cash = max(0.0, available)
|
||||
|
||||
for position in positions:
|
||||
code = position.stock_code
|
||||
tick = ticks.get(code)
|
||||
if code in runtime.account_cfg.excluded_codes:
|
||||
log.info("[Position - ] 代码=%s,名称=%s,止盈=跳过,补仓=跳过,原因=已配置为排除股票", code, position.stock_name)
|
||||
continue
|
||||
if (
|
||||
not code
|
||||
or position.open_price <= 0
|
||||
or position.volume <= 0
|
||||
or tick is None
|
||||
or tick.last_price <= 0
|
||||
):
|
||||
log.warning("[Position - ] 代码=%s,名称=%s,止盈=跳过,补仓=跳过,原因=持仓或行情数据无效", code or "未知", position.stock_name)
|
||||
continue
|
||||
try:
|
||||
code = position.stock_code
|
||||
tick = ticks.get(code)
|
||||
if code in runtime.account_cfg.excluded_codes:
|
||||
log.info("[Position - ] 代码=%s,名称=%s,止盈=跳过,补仓=跳过,原因=已配置为排除股票", code, position.stock_name)
|
||||
continue
|
||||
if (
|
||||
not code
|
||||
or position.open_price <= 0
|
||||
or position.volume <= 0
|
||||
or tick is None
|
||||
or tick.last_price <= 0
|
||||
):
|
||||
log.warning("[Position - ] 代码=%s,名称=%s,止盈=跳过,补仓=跳过,原因=持仓或行情数据无效", code or "未知", position.stock_name)
|
||||
continue
|
||||
|
||||
pnl_rate = round(
|
||||
(tick.last_price - position.open_price) / position.open_price * 100,
|
||||
2,
|
||||
)
|
||||
minimum_profit = calculate_min_profit_rate(position.open_price, 1)
|
||||
profit_decision = handle_profit(
|
||||
runtime=runtime,
|
||||
position=position,
|
||||
tick=tick,
|
||||
pnl_rate=pnl_rate,
|
||||
minimum_profit=minimum_profit,
|
||||
)
|
||||
profit_action = profit_decision.message or "未触发"
|
||||
loss_add_action = "未启用"
|
||||
if runtime.account_cfg.enable_loss_add_position and market_ok:
|
||||
loss_decision = handle_loss(
|
||||
pnl_rate = round(
|
||||
(tick.last_price - position.open_price) / position.open_price * 100,
|
||||
2,
|
||||
)
|
||||
minimum_profit = calculate_min_profit_rate(position.open_price, 1)
|
||||
profit_decision = handle_profit(
|
||||
runtime=runtime,
|
||||
position=position,
|
||||
tick=tick,
|
||||
pnl_rate=pnl_rate,
|
||||
available=remaining_cash,
|
||||
minimum_profit=minimum_profit,
|
||||
)
|
||||
remaining_cash -= loss_decision.reserved_cash
|
||||
loss_add_action = loss_decision.message or "未触发"
|
||||
elif runtime.account_cfg.enable_loss_add_position:
|
||||
loss_add_action = "大盘信号不允许"
|
||||
profit_action = profit_decision.message or "未触发"
|
||||
loss_add_action = "未启用"
|
||||
if runtime.account_cfg.enable_loss_add_position and market_ok:
|
||||
loss_decision = handle_loss(
|
||||
runtime=runtime,
|
||||
position=position,
|
||||
tick=tick,
|
||||
pnl_rate=pnl_rate,
|
||||
available=remaining_cash,
|
||||
)
|
||||
remaining_cash -= loss_decision.reserved_cash
|
||||
loss_add_action = loss_decision.message or "未触发"
|
||||
elif runtime.account_cfg.enable_loss_add_position:
|
||||
loss_add_action = "大盘信号不允许"
|
||||
|
||||
if pnl_rate>=0:
|
||||
log.info(
|
||||
"[Position ↑ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s",
|
||||
code, position.stock_name, pnl_rate, profit_action, loss_add_action,
|
||||
)
|
||||
else:
|
||||
log.info(
|
||||
"[Position ↓ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s",
|
||||
code, position.stock_name, pnl_rate, profit_action, loss_add_action,
|
||||
)
|
||||
if pnl_rate>=0:
|
||||
log.info(
|
||||
"[Position ↑ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s",
|
||||
code, position.stock_name, pnl_rate, profit_action, loss_add_action,
|
||||
)
|
||||
else:
|
||||
log.info(
|
||||
"[Position ↓ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s",
|
||||
code, position.stock_name, pnl_rate, profit_action, loss_add_action,
|
||||
)
|
||||
except Exception:
|
||||
log.exception("[Position] 持仓处理异常,代码=%s,继续处理后续持仓", position.stock_code)
|
||||
|
||||
|
||||
def handle_profit(
|
||||
|
||||
Reference in New Issue
Block a user