more tweaks

This commit is contained in:
pie
2026-05-09 00:18:42 +01:00
parent 9448c613ca
commit 0f5d00e292
3 changed files with 57 additions and 34 deletions
+38 -26
View File
@@ -19,6 +19,27 @@ class ExecutionManager:
self.tp_order_id = None
self.is_in_position = False
def _call_with_retry(self, func, *args, **kwargs):
"""Helper to call an API function with retries and jitter."""
import random
max_attempts = 5
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if '429' in str(e):
wait = (2 ** attempt) + random.uniform(0.1, 1.0)
logger.warning(f"Rate limited. Retrying in {wait:.1f}s...")
time.sleep(wait)
elif '400' in str(e) or '403' in str(e):
# For 400/403, logging the body is crucial
if hasattr(e, 'response') and e.response is not None:
logger.error(f"API Error Body: {e.response.text}")
raise e
else:
raise e
raise Exception(f"Failed after {max_attempts} attempts")
def execute_trade(self, params: Dict[str, Any], target_risk_amount: float = 0.0):
"""Starts the trade process by placing a MARKET entry order for immediate execution."""
isa_mode = os.getenv("ISA_MODE", "False").lower() == "true"
@@ -51,10 +72,11 @@ class ExecutionManager:
approx_price = params.get('current_price', params['entry_price'])
if self.is_etp:
# For ETPs, we default to 1.0 share for now as we don't have a live ETP price feed.
# Sizing for ETP: Default to 1.0 share if price unknown, otherwise could fetch.
quantity = 1.0
logger.info(f"Sizing for ETP {ticker}: Defaulting to 1.0 share (Leverage: {self.leverage}x)")
else:
# We must use round() to avoid 400 Bad Request
stop_loss = round(params['stop_loss'], 2)
risk_per_share = abs(approx_price - stop_loss)
if target_risk_amount > 0 and risk_per_share > 0:
@@ -69,7 +91,7 @@ class ExecutionManager:
logger.info(f"Placing immediate {direction} market order for {ticker} (Qty: {quantity})...")
try:
order = self.client.place_market_order(ticker, trade_quantity)
order = self._call_with_retry(self.client.place_market_order, ticker, trade_quantity)
self.current_order_id = order.get('id')
logger.info(f"Market order placed successfully. ID: {self.current_order_id}")
return True
@@ -77,17 +99,6 @@ class ExecutionManager:
logger.error(f"Failed to place entry market order: {e}")
return False
def _is_ticker_in_portfolio(self, ticker: str) -> bool:
"""Helper to check if a ticker currently has an open position."""
try:
positions = self.client.get_all_open_positions()
for pos in positions:
if pos.get('ticker') == ticker:
return True
except Exception as e:
logger.error(f"Error checking portfolio: {e}")
return False
def monitor_and_bracket(self, params: Dict[str, Any]):
"""Polls the entry and places SL/TP based on ACTUAL fill price."""
if not self.current_order_id:
@@ -111,7 +122,7 @@ class ExecutionManager:
return False
try:
positions = self.client.get_all_open_positions()
positions = self._call_with_retry(self.client.get_all_open_positions)
for pos in positions:
if pos.get('ticker') == ticker:
self.is_in_position = True
@@ -127,15 +138,12 @@ class ExecutionManager:
target_pct = params.get('target_percent', 1.0) / 100.0 # as decimal
if is_etp:
# ETP moves in 'opposite' direction to stock
# BUYing an Inverse ETP means we want it to go UP (Stock goes DOWN)
tp_move_pct = target_pct * leverage
sl_move_pct = tp_move_pct / 2.0 # 1:2 RR
tp_price = actual_entry_price * (1 + tp_move_pct)
sl_price = actual_entry_price * (1 - sl_move_pct)
# Since we bought the ETP, brackets are always SELL
tp_qty = -quantity
sl_qty = -quantity
else:
@@ -161,10 +169,13 @@ class ExecutionManager:
self.params['final_tp'] = tp_price
self.params['final_entry'] = actual_entry_price
# Jitter before placing brackets to avoid 429
time.sleep(random.uniform(0.5, 2.0))
try:
logger.info(f"Placing protection for {ticker} (Fill: {actual_entry_price:.2f}): TP @ {tp_price}, SL @ {sl_price}")
self.tp_order_id = self.client.place_limit_order(ticker, tp_qty, tp_price, time_validity="GOOD_TILL_CANCEL").get('id')
self.sl_order_id = self.client.place_stop_order(ticker, sl_qty, sl_price, time_validity="GOOD_TILL_CANCEL").get('id')
self.tp_order_id = self._call_with_retry(self.client.place_limit_order, ticker, tp_qty, tp_price, time_validity="GOOD_TILL_CANCEL").get('id')
self.sl_order_id = self._call_with_retry(self.client.place_stop_order, ticker, sl_qty, sl_price, time_validity="GOOD_TILL_CANCEL").get('id')
return True
except Exception as e:
logger.error(f"Failed to place SL/TP brackets: {e}")
@@ -180,7 +191,7 @@ class ExecutionManager:
try:
if self.tp_order_id:
try:
tp_info = self.client.get_order_status(self.tp_order_id)
tp_info = self._call_with_retry(self.client.get_order_status, self.tp_order_id)
if tp_info.get('status') == "FILLED":
fill_price = tp_info.get('filledPrice', tp_info.get('limitPrice', 0))
self.is_in_position = False
@@ -195,7 +206,7 @@ class ExecutionManager:
if self.sl_order_id:
try:
sl_info = self.client.get_order_status(self.sl_order_id)
sl_info = self._call_with_retry(self.client.get_order_status, self.sl_order_id)
if sl_info.get('status') == "FILLED":
fill_price = sl_info.get('filledPrice', sl_info.get('stopPrice', 0))
self.is_in_position = False
@@ -221,25 +232,26 @@ class ExecutionManager:
logger.info(f"Closing all orders and positions for {trading_ticker}...")
if self.current_order_id:
try: self.client.cancel_order(self.current_order_id)
try: self._call_with_retry(self.client.cancel_order, self.current_order_id)
except: pass
if self.sl_order_id:
try: self.client.cancel_order(self.sl_order_id)
try: self._call_with_retry(self.client.cancel_order, self.sl_order_id)
except: pass
if self.tp_order_id:
try: self.client.cancel_order(self.tp_order_id)
try: self._call_with_retry(self.client.cancel_order, self.tp_order_id)
except: pass
exit_price = 0.0
if self.is_in_position:
try:
positions = self.client.get_all_open_positions()
# Portfolio check with retry and jitter
positions = self._call_with_retry(self.client.get_all_open_positions)
for pos in positions:
if pos.get('ticker') == trading_ticker:
qty = float(pos.get('quantity', 0))
exit_price = float(pos.get('currentPrice', 0.0))
if qty != 0:
self.client.place_market_order(trading_ticker, -qty)
self._call_with_retry(self.client.place_market_order, trading_ticker, -qty)
break
except Exception as e:
logger.error(f"Failed to flatten position: {e}")