修复突破计算,执行器下单问题
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user