fix bug
This commit is contained in:
@@ -43,7 +43,7 @@ def open_signal(run:Runtime, ticks, open_signals) -> None:
|
|||||||
# 当前价高于昨收价可开仓
|
# 当前价高于昨收价可开仓
|
||||||
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, price)
|
||||||
log.info("[Open] %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("[Open] %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)
|
||||||
@@ -56,7 +56,7 @@ def open_signal(run:Runtime, ticks, open_signals) -> None:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
do_open(run,item.code,volume,item.signal_key)
|
do_open(run, item.code, volume, item.signal_key, price)
|
||||||
log.info("[Open] %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("[Open] %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)
|
||||||
@@ -64,7 +64,7 @@ def open_signal(run:Runtime, ticks, open_signals) -> None:
|
|||||||
log.exception("[Open] %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, price: float) -> None:
|
||||||
"""生成本地订单号并按最新价提交开仓委托。"""
|
"""生成本地订单号并按最新价提交开仓委托。"""
|
||||||
order_id = run.orders.new_order_id("base")
|
order_id = run.orders.new_order_id("base")
|
||||||
request = PlaceOrderRequest(
|
request = PlaceOrderRequest(
|
||||||
@@ -78,6 +78,16 @@ def do_open(run:Runtime,code:str,volume:int,signal_key:str)->None:
|
|||||||
if not run.orders.place(request):
|
if not run.orders.place(request):
|
||||||
raise RuntimeError("订单提交失败")
|
raise RuntimeError("订单提交失败")
|
||||||
|
|
||||||
|
run.state.set(StateItem(
|
||||||
|
code=code,
|
||||||
|
base_order_id=order_id,
|
||||||
|
base_qty=volume,
|
||||||
|
base_cost=round(price, 2),
|
||||||
|
base_status=STATUS_ING,
|
||||||
|
))
|
||||||
|
run.state.save()
|
||||||
|
run.open_watch.forget(code)
|
||||||
|
|
||||||
|
|
||||||
def check_timezone(timezone: str, now: datetime | None = None) -> bool:
|
def check_timezone(timezone: str, now: datetime | None = None) -> bool:
|
||||||
"""验证当前时间是否处于配置区间。
|
"""验证当前时间是否处于配置区间。
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ class OrderBook:
|
|||||||
self.mutex = Lock()
|
self.mutex = Lock()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def new_order_id(leg: str) -> str:
|
def new_order_id(_leg: str) -> str:
|
||||||
"""生成短订单号,为 QMT 备注中的信号键预留空间。"""
|
"""生成 ``trend-xxxxxxxx`` 格式的本地订单号。"""
|
||||||
return f"zt-{leg[:1]}-{secrets.token_hex(4)}"
|
return f"trend-{secrets.token_hex(4)}"
|
||||||
|
|
||||||
def busy(self, code: str, side: str) -> bool:
|
def busy(self, code: str, side: str) -> bool:
|
||||||
"""判断证券是否存在仍在处理中的同方向委托。"""
|
"""判断证券是否存在仍在处理中的同方向委托。"""
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class State:
|
|||||||
StateItem(
|
StateItem(
|
||||||
code=position.stock_code,
|
code=position.stock_code,
|
||||||
base_qty=position.volume,
|
base_qty=position.volume,
|
||||||
base_cost=position.open_price,
|
base_cost=round(position.open_price, 2),
|
||||||
base_status=STATUS_OK,
|
base_status=STATUS_OK,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -165,6 +165,12 @@ class State:
|
|||||||
# reconciliation must therefore happen before stale state is removed.
|
# reconciliation must therefore happen before stale state is removed.
|
||||||
for code in list(self.codes):
|
for code in list(self.codes):
|
||||||
if code not in position_codes:
|
if code not in position_codes:
|
||||||
|
item = self.get(code)
|
||||||
|
if any(
|
||||||
|
order_id and order_id in orders_by_local_id
|
||||||
|
for order_id in (item.base_order_id, item.added_order_id)
|
||||||
|
):
|
||||||
|
continue
|
||||||
if self.delete(code):
|
if self.delete(code):
|
||||||
log.info("[状态] 已移除持仓状态,代码=%s", code)
|
log.info("[状态] 已移除持仓状态,代码=%s", code)
|
||||||
self.save()
|
self.save()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from unittest.mock import patch
|
|||||||
from libs.grid_take_profit import GridState, GridTrailingTracker
|
from libs.grid_take_profit import GridState, GridTrailingTracker
|
||||||
from sdk import APIError, Assets, OrderItem, Portfolio, PositionItem, Tick
|
from sdk import APIError, Assets, OrderItem, Portfolio, PositionItem, Tick
|
||||||
from strategy.trend.order import OrderBook, PlaceOrderRequest
|
from strategy.trend.order import OrderBook, PlaceOrderRequest
|
||||||
|
from strategy.trend.open import do_open
|
||||||
from strategy.trend.positions import LOSS_TIERS, handle_loss, manage_positions
|
from strategy.trend.positions import LOSS_TIERS, handle_loss, manage_positions
|
||||||
from strategy.trend.boot import RunOnce
|
from strategy.trend.boot import RunOnce
|
||||||
from strategy.trend.state import STATUS_OK, STATUS_UNKNOWN, State, StateItem
|
from strategy.trend.state import STATUS_OK, STATUS_UNKNOWN, State, StateItem
|
||||||
@@ -42,6 +43,49 @@ class FailedOrderClient:
|
|||||||
|
|
||||||
|
|
||||||
class TrendTests(unittest.TestCase):
|
class TrendTests(unittest.TestCase):
|
||||||
|
def test_trend_order_id_format(self):
|
||||||
|
self.assertRegex(OrderBook.new_order_id("base"), r"^trend-[0-9a-f]{8}$")
|
||||||
|
|
||||||
|
def test_open_records_base_order_and_rounded_cost(self):
|
||||||
|
with TemporaryDirectory() as directory:
|
||||||
|
state = State.for_strategy(directory, "trend", "A")
|
||||||
|
forgotten = []
|
||||||
|
runtime = SimpleNamespace(
|
||||||
|
client=FakeClient(),
|
||||||
|
orders=OrderBook(),
|
||||||
|
state=state,
|
||||||
|
open_watch=SimpleNamespace(forget=forgotten.append),
|
||||||
|
)
|
||||||
|
|
||||||
|
do_open(runtime, "000001.SZ", 100, "morning", 12.345)
|
||||||
|
|
||||||
|
item = state.get("000001.SZ")
|
||||||
|
self.assertRegex(item.base_order_id, r"^trend-[0-9a-f]{8}$")
|
||||||
|
self.assertEqual(item.base_qty, 100)
|
||||||
|
self.assertEqual(item.base_cost, 12.35)
|
||||||
|
self.assertEqual(item.base_status, "ING")
|
||||||
|
self.assertEqual(forgotten, ["000001.SZ"])
|
||||||
|
|
||||||
|
def test_pending_base_order_survives_position_delay(self):
|
||||||
|
with TemporaryDirectory() as directory:
|
||||||
|
state = State.for_strategy(directory, "trend", "A")
|
||||||
|
state.set(StateItem(
|
||||||
|
"000001.SZ",
|
||||||
|
base_order_id="trend-12345678",
|
||||||
|
base_status="ING",
|
||||||
|
))
|
||||||
|
pending = OrderItem(
|
||||||
|
"1", "000001.SZ", "BUY", "", "50", None, 100,
|
||||||
|
local_order_id="trend-12345678",
|
||||||
|
)
|
||||||
|
|
||||||
|
state.reconcile([], [pending])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
state.get("000001.SZ").base_order_id,
|
||||||
|
"trend-12345678",
|
||||||
|
)
|
||||||
|
|
||||||
def test_grid_states_and_account_isolation(self):
|
def test_grid_states_and_account_isolation(self):
|
||||||
tracker = GridTrailingTracker(1)
|
tracker = GridTrailingTracker(1)
|
||||||
self.assertEqual(tracker.observe("A:code", 2.1).state, GridState.ARMED)
|
self.assertEqual(tracker.observe("A:code", 2.1).state, GridState.ARMED)
|
||||||
|
|||||||
Reference in New Issue
Block a user