KarlQuant commited on
Commit
7282975
Β·
verified Β·
1 Parent(s): 54e270e

Update Quasar_axrvi_ranker.py

Browse files
Files changed (1) hide show
  1. 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
- logger.info(f"βœ… Trade confirmed | contract_id={msg['buy'].get('contract_id')}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: