Spaces:
Running
Running
Update Quasar_axrvi_ranker.py
Browse files- Quasar_axrvi_ranker.py +92 -1
Quasar_axrvi_ranker.py
CHANGED
|
@@ -415,6 +415,7 @@ class Trade:
|
|
| 415 |
unrealized_pnl: float = 0.0
|
| 416 |
realized_pnl: float = 0.0
|
| 417 |
fees: float = 0.0
|
|
|
|
| 418 |
|
| 419 |
def compute_unrealized_pnl(self, current_price: float) -> float:
|
| 420 |
if self.direction == TradeDirection.LONG:
|
|
@@ -3175,6 +3176,12 @@ class QuasarAXRVIBridge:
|
|
| 3175 |
# [S8] Per-trade tick counters for minimum holding check (optimal stopping)
|
| 3176 |
self._trade_tick_counts: Dict[str, int] = {}
|
| 3177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3178 |
self._sync_thread: Optional[threading.Thread] = None
|
| 3179 |
self._sync_loop: Optional[asyncio.AbstractEventLoop] = None
|
| 3180 |
|
|
@@ -3318,7 +3325,25 @@ class QuasarAXRVIBridge:
|
|
| 3318 |
if "tick" in msg:
|
| 3319 |
self._on_price_tick(msg["tick"])
|
| 3320 |
elif "buy" in msg:
|
| 3321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3322 |
elif "error" in msg:
|
| 3323 |
logger.error(f"β οΈ Deriv error: {msg['error']}")
|
| 3324 |
if self.ranker_logger:
|
|
@@ -3389,6 +3414,47 @@ class QuasarAXRVIBridge:
|
|
| 3389 |
direction = TradeDirection.LONG if action == "BUY" else TradeDirection.SHORT
|
| 3390 |
self.position_mgr.open_trade(trade_id, asset, direction, price, quantity)
|
| 3391 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3392 |
# Register committed notional with risk manager
|
| 3393 |
self.portfolio_risk_mgr.register_open(trade_id, quantity * price)
|
| 3394 |
|
|
@@ -3407,6 +3473,31 @@ class QuasarAXRVIBridge:
|
|
| 3407 |
# ββ Position monitoring ββββββββββββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββββββββββββ
|
| 3408 |
|
| 3409 |
async def _close_position(self, trade_id: str, exit_price: float) -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3410 |
fees = exit_price * self.trade_config.commission_rate
|
| 3411 |
trade = self.position_mgr.close_trade(trade_id, exit_price, fees)
|
| 3412 |
if not trade:
|
|
|
|
| 415 |
unrealized_pnl: float = 0.0
|
| 416 |
realized_pnl: float = 0.0
|
| 417 |
fees: float = 0.0
|
| 418 |
+
contract_id: Optional[str] = None # Deriv contract ID β set on buy confirmation
|
| 419 |
|
| 420 |
def compute_unrealized_pnl(self, current_price: float) -> float:
|
| 421 |
if self.direction == TradeDirection.LONG:
|
|
|
|
| 3176 |
# [S8] Per-trade tick counters for minimum holding check (optimal stopping)
|
| 3177 |
self._trade_tick_counts: Dict[str, int] = {}
|
| 3178 |
|
| 3179 |
+
# ββ Deriv req_id β trade_id mapping (for contract_id binding) ββββββββ
|
| 3180 |
+
# When we send a buy, we record req_idβtrade_id here so that when the
|
| 3181 |
+
# buy confirmation arrives in _on_deriv_message we can bind the
|
| 3182 |
+
# contract_id back to the correct Trade object.
|
| 3183 |
+
self._pending_req_to_trade: Dict[int, str] = {}
|
| 3184 |
+
|
| 3185 |
self._sync_thread: Optional[threading.Thread] = None
|
| 3186 |
self._sync_loop: Optional[asyncio.AbstractEventLoop] = None
|
| 3187 |
|
|
|
|
| 3325 |
if "tick" in msg:
|
| 3326 |
self._on_price_tick(msg["tick"])
|
| 3327 |
elif "buy" in msg:
|
| 3328 |
+
contract_id = msg["buy"].get("contract_id")
|
| 3329 |
+
req_id = msg.get("req_id")
|
| 3330 |
+
trade_id = self._pending_req_to_trade.pop(req_id, None)
|
| 3331 |
+
if trade_id and contract_id:
|
| 3332 |
+
# Bind the Deriv contract_id to the internal Trade object so
|
| 3333 |
+
# _close_position can send a sell for early exit
|
| 3334 |
+
with self.position_mgr._lock:
|
| 3335 |
+
trade = self.position_mgr._open_trades.get(trade_id)
|
| 3336 |
+
if trade:
|
| 3337 |
+
trade.contract_id = str(contract_id)
|
| 3338 |
+
logger.info(
|
| 3339 |
+
f"β
Trade confirmed | trade_id={trade_id} | "
|
| 3340 |
+
f"contract_id={contract_id}"
|
| 3341 |
+
)
|
| 3342 |
+
else:
|
| 3343 |
+
logger.info(
|
| 3344 |
+
f"β
Buy confirmation received | contract_id={contract_id} | "
|
| 3345 |
+
f"req_id={req_id} (trade_id lookup: {trade_id})"
|
| 3346 |
+
)
|
| 3347 |
elif "error" in msg:
|
| 3348 |
logger.error(f"β οΈ Deriv error: {msg['error']}")
|
| 3349 |
if self.ranker_logger:
|
|
|
|
| 3414 |
direction = TradeDirection.LONG if action == "BUY" else TradeDirection.SHORT
|
| 3415 |
self.position_mgr.open_trade(trade_id, asset, direction, price, quantity)
|
| 3416 |
|
| 3417 |
+
# ββ Send actual buy order to Deriv API ββββββββββββββββββββββββββββββββ
|
| 3418 |
+
# Everything above is internal bookkeeping; THIS is what places the real
|
| 3419 |
+
# trade on Deriv. Without this call the contract never exists on their end.
|
| 3420 |
+
deriv_symbol = SYMBOL_MAP_REVERSE.get(asset)
|
| 3421 |
+
if deriv_symbol and self.ws_client and self.ws_client.connected:
|
| 3422 |
+
contract_type = "CALL" if action == "BUY" else "PUT"
|
| 3423 |
+
buy_msg = {
|
| 3424 |
+
"buy": "1",
|
| 3425 |
+
"parameters": {
|
| 3426 |
+
"amount": self.trade_config.amount,
|
| 3427 |
+
"basis": "stake",
|
| 3428 |
+
"contract_type": contract_type,
|
| 3429 |
+
"currency": "USD",
|
| 3430 |
+
"duration": self.trade_config.expiry_time,
|
| 3431 |
+
"duration_unit": "s",
|
| 3432 |
+
"symbol": deriv_symbol,
|
| 3433 |
+
},
|
| 3434 |
+
}
|
| 3435 |
+
sent = await self.ws_client.send_message(buy_msg)
|
| 3436 |
+
if sent:
|
| 3437 |
+
# send_message stamps req_id on buy_msg in-place β record the
|
| 3438 |
+
# mapping so _on_deriv_message can bind contract_id to this trade
|
| 3439 |
+
self._pending_req_to_trade[buy_msg["req_id"]] = trade_id
|
| 3440 |
+
logger.info(
|
| 3441 |
+
f"[{asset}] π€ Deriv BUY sent | "
|
| 3442 |
+
f"contract={contract_type} | "
|
| 3443 |
+
f"amount={self.trade_config.amount} | "
|
| 3444 |
+
f"duration={self.trade_config.expiry_time}s | "
|
| 3445 |
+
f"req_id={buy_msg['req_id']}"
|
| 3446 |
+
)
|
| 3447 |
+
else:
|
| 3448 |
+
logger.error(
|
| 3449 |
+
f"[{asset}] β Deriv BUY send FAILED β ws_client.send_message returned False"
|
| 3450 |
+
)
|
| 3451 |
+
else:
|
| 3452 |
+
logger.error(
|
| 3453 |
+
f"[{asset}] β Cannot send Deriv BUY β "
|
| 3454 |
+
f"deriv_symbol={deriv_symbol} | "
|
| 3455 |
+
f"ws_connected={self.ws_client.connected if self.ws_client else 'no client'}"
|
| 3456 |
+
)
|
| 3457 |
+
|
| 3458 |
# Register committed notional with risk manager
|
| 3459 |
self.portfolio_risk_mgr.register_open(trade_id, quantity * price)
|
| 3460 |
|
|
|
|
| 3473 |
# ββ Position monitoring ββββββββββββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββββββββββββ
|
| 3474 |
|
| 3475 |
async def _close_position(self, trade_id: str, exit_price: float) -> None:
|
| 3476 |
+
# ββ Early-exit sell on Deriv ββββββββββββββββββββββββββββββββββββββββββ
|
| 3477 |
+
# Retrieve contract_id BEFORE close_trade() removes the trade from
|
| 3478 |
+
# _open_trades. price=0 means "sell at best available market price".
|
| 3479 |
+
with self.position_mgr._lock:
|
| 3480 |
+
open_trade = self.position_mgr._open_trades.get(trade_id)
|
| 3481 |
+
cid = open_trade.contract_id if open_trade else None
|
| 3482 |
+
|
| 3483 |
+
if cid and self.ws_client and self.ws_client.connected:
|
| 3484 |
+
sell_sent = await self.ws_client.send_message({
|
| 3485 |
+
"sell": cid,
|
| 3486 |
+
"price": 0, # 0 = market price / best available
|
| 3487 |
+
})
|
| 3488 |
+
if sell_sent:
|
| 3489 |
+
logger.info(f"[{trade_id}] π€ Deriv SELL sent | contract_id={cid}")
|
| 3490 |
+
else:
|
| 3491 |
+
logger.error(f"[{trade_id}] β Deriv SELL send FAILED | contract_id={cid}")
|
| 3492 |
+
elif not cid:
|
| 3493 |
+
# contract_id was never bound β buy confirmation hasn't arrived yet
|
| 3494 |
+
# or trade was opened before this fix was deployed. Log and continue
|
| 3495 |
+
# so internal bookkeeping still closes cleanly.
|
| 3496 |
+
logger.warning(
|
| 3497 |
+
f"[{trade_id}] β οΈ No contract_id bound β cannot send Deriv SELL. "
|
| 3498 |
+
f"Contract may expire naturally on Deriv side."
|
| 3499 |
+
)
|
| 3500 |
+
|
| 3501 |
fees = exit_price * self.trade_config.commission_rate
|
| 3502 |
trade = self.position_mgr.close_trade(trade_id, exit_price, fees)
|
| 3503 |
if not trade:
|