feat: Refactor settings UI and fix commission fee recording
Settings improvements: - Reorganize config groups with logical ordering (server, auth, ai, trading, etc.) - Add description/tooltip for each config item with question mark icon - Add icon to each group header - Support i18n for descriptions (zh-CN, zh-TW, en-US) - Move order execution config (ORDER_MODE, MAKER_WAIT_SEC, MAKER_OFFSET_BPS) to env Commission fee fixes: - Fix fee extraction in exchange clients: Bybit, Coinbase, Kraken, Gate, Kucoin, Bitfinex - Properly accumulate and record commission fees in pending_order_worker - Add fee/fee_ccy fields to wait_for_fill returns Frontend updates: - Remove order_mode config from trading-assistant frontend (now uses env config) - Add sorted schema display by order field - Add tooltip with description on hover
This commit is contained in:
@@ -234,8 +234,11 @@ class BitfinexDerivativesClient(BitfinexClient):
|
||||
last = last or []
|
||||
filled = 0.0
|
||||
avg_price = 0.0
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
status = ""
|
||||
# best-effort parsing from array fields
|
||||
# Bitfinex order response format: [ID, GID, CID, SYMBOL, MTS_CREATE, MTS_UPDATE, AMOUNT, AMOUNT_ORIG, TYPE, TYPE_PREV, MTS_TIF, _, FLAGS, STATUS, _, PRICE_AVG, ...]
|
||||
try:
|
||||
if isinstance(last, list) and len(last) >= 15:
|
||||
status = str(last[13] or "")
|
||||
@@ -245,12 +248,14 @@ class BitfinexDerivativesClient(BitfinexClient):
|
||||
avg_price = float(last[14] or 0.0)
|
||||
except Exception:
|
||||
pass
|
||||
# Note: Bitfinex order response doesn't include fee; fee is typically in trades.
|
||||
# We return 0.0 here; actual fee can be fetched via trades endpoint if needed.
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if isinstance(status, str) and ("EXECUTED" in status.upper() or "CANCELED" in status.upper()):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
|
||||
|
||||
@@ -315,12 +315,22 @@ class BybitClient(BaseRestClient):
|
||||
avg_price = float(last.get("avgPrice") or 0.0)
|
||||
except Exception:
|
||||
avg_price = 0.0
|
||||
# Extract fee from cumExecFee (Bybit API field for cumulative execution fee)
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
try:
|
||||
fee = abs(float(last.get("cumExecFee") or 0.0))
|
||||
except Exception:
|
||||
fee = 0.0
|
||||
# Bybit linear contracts are settled in USDT
|
||||
if fee > 0:
|
||||
fee_ccy = "USDT"
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if status.lower() in ("filled", "cancelled", "canceled", "rejected"):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
def get_positions(self) -> Dict[str, Any]:
|
||||
|
||||
@@ -170,6 +170,8 @@ class CoinbaseExchangeClient(BaseRestClient):
|
||||
status = str(last.get("status") or "")
|
||||
filled = 0.0
|
||||
avg_price = 0.0
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
try:
|
||||
filled = float(last.get("filled_size") or 0.0)
|
||||
except Exception:
|
||||
@@ -180,12 +182,20 @@ class CoinbaseExchangeClient(BaseRestClient):
|
||||
avg_price = executed_value / filled
|
||||
except Exception:
|
||||
avg_price = 0.0
|
||||
# Extract fee from fill_fees (Coinbase API field)
|
||||
try:
|
||||
fee = abs(float(last.get("fill_fees") or 0.0))
|
||||
except Exception:
|
||||
fee = 0.0
|
||||
# Coinbase fees are typically in the quote currency (e.g., USD)
|
||||
if fee > 0:
|
||||
fee_ccy = "USD"
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if status.lower() in ("done", "rejected", "canceled", "cancelled"):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
|
||||
|
||||
@@ -134,6 +134,8 @@ class GateSpotClient(_GateBase):
|
||||
status = str(last.get("status") or "")
|
||||
filled = 0.0
|
||||
avg_price = 0.0
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
try:
|
||||
filled = float(last.get("filled_amount") or 0.0)
|
||||
except Exception:
|
||||
@@ -144,12 +146,18 @@ class GateSpotClient(_GateBase):
|
||||
avg_price = filled_total / filled
|
||||
except Exception:
|
||||
avg_price = 0.0
|
||||
# Extract fee from Gate API
|
||||
try:
|
||||
fee = abs(float(last.get("fee") or 0.0))
|
||||
except Exception:
|
||||
fee = 0.0
|
||||
fee_ccy = str(last.get("fee_currency") or "").strip()
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if status.lower() in ("closed", "cancelled", "canceled"):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
|
||||
@@ -321,6 +329,8 @@ class GateUsdtFuturesClient(_GateBase):
|
||||
status = str(last.get("status") or "")
|
||||
filled = 0.0
|
||||
avg_price = 0.0
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
try:
|
||||
# Gate futures often returns "filled_size" in contracts.
|
||||
filled_ct = abs(float(last.get("filled_size") or last.get("filledSize") or 0.0))
|
||||
@@ -331,12 +341,20 @@ class GateUsdtFuturesClient(_GateBase):
|
||||
avg_price = float(last.get("fill_price") or last.get("fillPrice") or last.get("price") or 0.0)
|
||||
except Exception:
|
||||
avg_price = 0.0
|
||||
# Extract fee from Gate Futures API
|
||||
try:
|
||||
fee = abs(float(last.get("fee") or 0.0))
|
||||
except Exception:
|
||||
fee = 0.0
|
||||
# Gate USDT futures fees are in USDT
|
||||
if fee > 0:
|
||||
fee_ccy = "USDT"
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if str(status).lower() in ("finished", "cancelled", "canceled"):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@ class KrakenClient(BaseRestClient):
|
||||
status = str(last.get("status") or "")
|
||||
filled = 0.0
|
||||
avg_price = 0.0
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
try:
|
||||
filled = float(last.get("vol_exec") or 0.0)
|
||||
except Exception:
|
||||
@@ -164,12 +166,20 @@ class KrakenClient(BaseRestClient):
|
||||
avg_price = cost / filled
|
||||
except Exception:
|
||||
avg_price = 0.0
|
||||
# Extract fee from Kraken API
|
||||
try:
|
||||
fee = abs(float(last.get("fee") or 0.0))
|
||||
except Exception:
|
||||
fee = 0.0
|
||||
# Kraken fees are typically in the quote currency (e.g., USD)
|
||||
if fee > 0:
|
||||
fee_ccy = "USD"
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if status.lower() in ("closed", "canceled", "cancelled", "expired"):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ class KrakenFuturesClient(BaseRestClient):
|
||||
status = str(last.get("status") or last.get("orderStatus") or "")
|
||||
filled = 0.0
|
||||
avg_price = 0.0
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
try:
|
||||
filled = float(last.get("filledSize") or last.get("filled_size") or 0.0)
|
||||
except Exception:
|
||||
@@ -194,12 +196,20 @@ class KrakenFuturesClient(BaseRestClient):
|
||||
avg_price = float(last.get("avgFillPrice") or last.get("avg_fill_price") or 0.0)
|
||||
except Exception:
|
||||
avg_price = 0.0
|
||||
# Extract fee from Kraken Futures API (if available)
|
||||
try:
|
||||
fee = abs(float(last.get("fee") or 0.0))
|
||||
except Exception:
|
||||
fee = 0.0
|
||||
# Kraken Futures fees are typically in USD
|
||||
if fee > 0:
|
||||
fee_ccy = "USD"
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if status.lower() in ("filled", "cancelled", "canceled", "rejected"):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
|
||||
|
||||
@@ -475,6 +475,8 @@ class KucoinFuturesClient(BaseRestClient):
|
||||
status = str(od.get("status") or "")
|
||||
filled = 0.0
|
||||
avg_price = 0.0
|
||||
fee = 0.0
|
||||
fee_ccy = ""
|
||||
try:
|
||||
# dealSize is in contracts; convert back to base using multiplier best-effort.
|
||||
deal_ct = float(od.get("dealSize") or 0.0)
|
||||
@@ -497,12 +499,20 @@ class KucoinFuturesClient(BaseRestClient):
|
||||
filled = abs(float(deal_ct or 0.0)) * float(mult)
|
||||
if filled > 0 and deal_value > 0:
|
||||
avg_price = float(deal_value) / float(filled)
|
||||
# Extract fee from KuCoin Futures API (orderMargin contains fee info in some cases)
|
||||
try:
|
||||
fee = abs(float(od.get("fee") or od.get("orderFee") or 0.0))
|
||||
except Exception:
|
||||
fee = 0.0
|
||||
# KuCoin Futures fees are typically in USDT
|
||||
if fee > 0:
|
||||
fee_ccy = "USDT"
|
||||
if filled > 0 and avg_price > 0:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if status.lower() in ("done", "canceled", "cancelled", "filled"):
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
if time.time() >= end_ts:
|
||||
return {"filled": filled, "avg_price": avg_price, "status": status, "order": last}
|
||||
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
|
||||
time.sleep(float(poll_interval_sec or 0.5))
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from app.services.signal_notifier import SignalNotifier
|
||||
from app.services.exchange_execution import load_strategy_configs, resolve_exchange_config, safe_exchange_config_for_log
|
||||
@@ -722,12 +722,17 @@ class PendingOrderWorker:
|
||||
_notify_live_best_effort(status="failed", error="spot_market_does_not_support_short_signals")
|
||||
return
|
||||
|
||||
# Unified maker->market fallback settings (defaults: 10 seconds)
|
||||
order_mode = str(payload.get("order_mode") or payload.get("orderMode") or "maker").strip().lower()
|
||||
maker_wait_sec = float(payload.get("maker_wait_sec") or payload.get("makerWaitSec") or 10.0)
|
||||
maker_offset_bps = float(payload.get("maker_offset_bps") or payload.get("makerOffsetBps") or 2.0)
|
||||
# Unified maker->market fallback settings
|
||||
# Priority: payload config > environment variable > default value
|
||||
_default_order_mode = os.getenv("ORDER_MODE", "maker").strip().lower()
|
||||
_default_maker_wait_sec = float(os.getenv("MAKER_WAIT_SEC", "10"))
|
||||
_default_maker_offset_bps = float(os.getenv("MAKER_OFFSET_BPS", "2"))
|
||||
|
||||
order_mode = str(payload.get("order_mode") or payload.get("orderMode") or _default_order_mode).strip().lower()
|
||||
maker_wait_sec = float(payload.get("maker_wait_sec") or payload.get("makerWaitSec") or _default_maker_wait_sec)
|
||||
maker_offset_bps = float(payload.get("maker_offset_bps") or payload.get("makerOffsetBps") or _default_maker_offset_bps)
|
||||
if maker_wait_sec <= 0:
|
||||
maker_wait_sec = 10.0
|
||||
maker_wait_sec = _default_maker_wait_sec if _default_maker_wait_sec > 0 else 10.0
|
||||
if maker_offset_bps < 0:
|
||||
maker_offset_bps = 0.0
|
||||
maker_offset = maker_offset_bps / 10000.0
|
||||
@@ -1070,18 +1075,22 @@ class PendingOrderWorker:
|
||||
q = client.wait_for_fill(symbol=str(symbol), order_id=limit_order_id, client_order_id=limit_client_oid, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, CoinbaseExchangeClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, client_order_id=limit_client_oid, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, KrakenClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, KrakenFuturesClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, client_order_id=limit_client_oid, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, KucoinSpotClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
@@ -1091,22 +1100,27 @@ class PendingOrderWorker:
|
||||
q = client.wait_for_fill(order_id=limit_order_id, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, GateSpotClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, GateUsdtFuturesClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, contract=to_gate_currency_pair(str(symbol)), max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, BitfinexClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
elif isinstance(client, BitfinexDerivativesClient):
|
||||
q = client.wait_for_fill(order_id=limit_order_id, max_wait_sec=maker_wait_sec)
|
||||
phases["limit_query"] = q
|
||||
_apply_fill(float(q.get("filled") or 0.0), float(q.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q.get("fee") or 0.0), str(q.get("fee_ccy") or ""))
|
||||
|
||||
remaining = max(0.0, float(amount or 0.0) - total_base)
|
||||
|
||||
@@ -1386,18 +1400,22 @@ class PendingOrderWorker:
|
||||
q2 = client.wait_for_fill(symbol=str(symbol), order_id=market_order_id, client_order_id=market_client_oid, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, CoinbaseExchangeClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, client_order_id=market_client_oid, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, KrakenClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, KrakenFuturesClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, client_order_id=market_client_oid, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, KucoinSpotClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
@@ -1407,22 +1425,27 @@ class PendingOrderWorker:
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, GateSpotClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, GateUsdtFuturesClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, contract=to_gate_currency_pair(str(symbol)), max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, BitfinexClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
elif isinstance(client, BitfinexDerivativesClient):
|
||||
q2 = client.wait_for_fill(order_id=market_order_id, max_wait_sec=3.0)
|
||||
phases["market_query"] = q2
|
||||
_apply_fill(float(q2.get("filled") or 0.0), float(q2.get("avg_price") or 0.0))
|
||||
_apply_fee(float(q2.get("fee") or 0.0), str(q2.get("fee_ccy") or ""))
|
||||
except LiveTradingError as e:
|
||||
logger.warning(f"live market phase failed: pending_id={order_id}, strategy_id={strategy_id}, cfg={safe_cfg}, err={e}")
|
||||
phases["market_error"] = str(e)
|
||||
|
||||
@@ -2221,8 +2221,11 @@ class TradingExecutor:
|
||||
margin_mode: str = 'cross',
|
||||
stop_loss_price: float = None,
|
||||
take_profit_price: float = None,
|
||||
order_mode: str = 'maker',
|
||||
maker_wait_sec: float = 8.0,
|
||||
# Order execution params (order_mode, maker_wait_sec, maker_offset_bps) are now
|
||||
# configured via environment variables: ORDER_MODE, MAKER_WAIT_SEC, MAKER_OFFSET_BPS
|
||||
# These parameters are kept for backward compatibility but will be ignored.
|
||||
order_mode: str = None,
|
||||
maker_wait_sec: float = None,
|
||||
maker_retries: int = 3,
|
||||
close_fallback_to_market: bool = True,
|
||||
open_fallback_to_market: bool = True,
|
||||
@@ -2236,6 +2239,9 @@ class TradingExecutor:
|
||||
A separate worker will poll `pending_orders` and dispatch:
|
||||
- execution_mode='signal': dispatch notifications (no real trading).
|
||||
- execution_mode='live': reserved for future live trading execution (not implemented).
|
||||
|
||||
Note: Order execution settings (order_mode, maker_wait_sec, maker_offset_bps) are now
|
||||
configured via environment variables and not passed from strategy config.
|
||||
"""
|
||||
try:
|
||||
# Reference price at enqueue time: use current tick price if provided to avoid extra fetch.
|
||||
@@ -2249,8 +2255,7 @@ class TradingExecutor:
|
||||
"stop_loss_price": float(stop_loss_price or 0.0) if stop_loss_price is not None else 0.0,
|
||||
"take_profit_price": float(take_profit_price or 0.0) if take_profit_price is not None else 0.0,
|
||||
"margin_mode": str(margin_mode or "cross"),
|
||||
"order_mode": str(order_mode or "maker"),
|
||||
"maker_wait_sec": float(maker_wait_sec or 0.0),
|
||||
# Order execution params moved to env config (ORDER_MODE, MAKER_WAIT_SEC, MAKER_OFFSET_BPS)
|
||||
"maker_retries": int(maker_retries or 0),
|
||||
"close_fallback_to_market": bool(close_fallback_to_market),
|
||||
"open_fallback_to_market": bool(open_fallback_to_market),
|
||||
|
||||
Reference in New Issue
Block a user