修复突破计算,执行器下单问题

This commit is contained in:
dekun
2026-05-23 17:18:38 +08:00
parent bfde4b60c6
commit ffba2e60e6
9 changed files with 242 additions and 38 deletions
+61 -7
View File
@@ -256,6 +256,46 @@ def _tp_sl_triggers(side: str, tp_price: str, sl_price: str) -> tuple[dict[str,
return tp_tr, sl_tr
def _order_filled_abs(order: dict[str, Any]) -> float:
"""市价单已成交张数(绝对值):|size| - |left|。"""
total = abs(_float(order.get("size")))
left = abs(_float(order.get("left")))
filled = total - left
if filled > 1e-12:
return filled
# 部分 Gate 响应在 finished 时 left 为空,用 finish_as / status 兜底
if str(order.get("status") or "") == "finished" and total > 1e-12 and left <= 1e-12:
return total
return 0.0
def _market_fill_accepted(
order: dict[str, Any],
*,
net_size: float,
order_size_min: float,
) -> tuple[bool, float, str]:
"""
判定市价是否已有有效成交(含 IOC 部分成交)。
返回 (accepted, filled_abs, note)。
"""
filled = _order_filled_abs(order)
net_abs = abs(net_size)
effective = max(filled, net_abs)
min_sz = max(float(order_size_min), 1e-12)
if effective + 1e-12 < min_sz:
return False, effective, "below_order_size_min"
st = str(order.get("status") or "")
finish = str(order.get("finish_as") or "")
if effective >= min_sz:
if st == "finished" or net_abs >= min_sz:
note = "partial_fill" if filled > 1e-12 and abs(_float(order.get("left"))) > 1e-12 else "filled"
if finish and finish not in {"filled", "ioc", ""} and net_abs < min_sz:
return False, effective, f"finish_as={finish}"
return True, effective, note
return False, effective, "no_fill"
async def execute_signal_live(settings: Settings, sig: TradeSignal) -> dict:
"""
市价开仓 + 计划委托止盈/止损(reduce_only 市价 IOC)。
@@ -329,13 +369,21 @@ async def execute_signal_live(settings: Settings, sig: TradeSignal) -> dict:
if not isinstance(order, dict):
return {"status": "error", "reason": "order_response_invalid"}
st = str(order.get("status") or "")
finish = str(order.get("finish_as") or "")
left_abs = abs(_float(order.get("left")))
if st != "finished" or left_abs > 1e-12:
return {"status": "error", "reason": "market_not_filled", "order": order}
if finish and finish not in {"filled", "ioc"}:
return {"status": "error", "reason": "market_not_filled", "order": order}
net_size = await fetch_net_position_size(client, contract)
fill_ok, filled_abs, fill_note = _market_fill_accepted(
order,
net_size=net_size,
order_size_min=order_size_min,
)
if not fill_ok:
return {
"status": "error",
"reason": "market_not_filled",
"detail": fill_note,
"order": order,
"net_position_size": net_size,
"filled_abs": filled_abs,
}
tp_s = _format_trigger_price(float(sig.take_profit), price_tick)
sl_s = _format_trigger_price(float(sig.stop_loss), price_tick)
@@ -368,6 +416,9 @@ async def execute_signal_live(settings: Settings, sig: TradeSignal) -> dict:
"signal_id": sig.signal_id,
"market_order": order,
"sized_contracts": open_size,
"filled_contracts": filled_abs,
"fill_note": fill_note,
"net_position_size": net_size,
"risk_budget_usdt": round(risk_usdt, 6),
"reference_entry": entry,
"trigger_price_tick": str(price_tick) if price_tick is not None else None,
@@ -420,6 +471,9 @@ async def execute_signal_live(settings: Settings, sig: TradeSignal) -> dict:
"take_profit_order": tp_resp,
"stop_loss_order": sl_resp,
"sized_contracts": open_size,
"filled_contracts": filled_abs,
"fill_note": fill_note,
"net_position_size": net_size,
"risk_budget_usdt": round(risk_usdt, 6),
"reference_entry": entry,
"trigger_price_tick": str(price_tick) if price_tick is not None else None,
+6 -3
View File
@@ -57,9 +57,12 @@ async def list_futures_positions(
if abs(_float_field(row.get("size"))) <= 1e-12:
continue
out.append(_slim_futures_position(row))
if len(out) >= cap:
break
return out, None
out.sort(
key=lambda r: abs(_float_field(r.get("size")))
* max(_float_field(r.get("mark_price"), _float_field(r.get("entry_price"), 1.0)), 1e-9),
reverse=True,
)
return out[:cap], None
except Exception as exc: # noqa: BLE001
return None, str(exc)
@@ -0,0 +1,21 @@
from __future__ import annotations
import unittest
from app.gate_futures_live import _market_fill_accepted, _order_filled_abs
class TestMarketFill(unittest.TestCase):
def test_partial_ioc_fill_by_net_position(self):
order = {"status": "finished", "finish_as": "ioc", "size": "-1", "left": "-0.6"}
ok, filled, note = _market_fill_accepted(order, net_size=-0.4, order_size_min=0.1)
self.assertTrue(ok)
self.assertAlmostEqual(filled, 0.4, places=6)
def test_order_filled_abs(self):
self.assertAlmostEqual(_order_filled_abs({"size": "0.4", "left": "0"}), 0.4, places=6)
self.assertAlmostEqual(_order_filled_abs({"size": "-1", "left": "-0.6"}), 0.4, places=6)
if __name__ == "__main__":
unittest.main()