more tweaks
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
Looking at the logs it appears that specifically SELL orders are not getting through
|
||||
|
||||
When making that first trade are we specifying the price we want? I would expect immediate orders to go through in seconds, but the initial order appears to sit as "NEW" for some time... I understand calculations are made, but make a more approx calculation based on the price we are reacting to, then properly calulate SL and TP from the fullfilled order?
|
||||
in fact are there any indications that other than the initial trade are the SL and TP order going through?
|
||||
+38
-26
@@ -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}")
|
||||
|
||||
@@ -84,11 +84,12 @@ class TouchTurnStrategy:
|
||||
low = opening_candle['Low']
|
||||
open_p = opening_candle['Open']
|
||||
close_p = opening_candle['Close']
|
||||
range_size = high - low
|
||||
self.range_size = high - low
|
||||
self.current_price = close_p
|
||||
|
||||
# 1. Liquidity Filter
|
||||
if range_size < (daily_atr * self.risk_percent_atr / 100):
|
||||
logger.info(f"Setup invalid: Range ({range_size:.2f}) < 25% of ATR ({daily_atr:.2f})")
|
||||
if self.range_size < (daily_atr * self.risk_percent_atr / 100):
|
||||
logger.info(f"Setup invalid: Range ({self.range_size:.2f}) < 25% of ATR ({daily_atr:.2f})")
|
||||
self.valid_setup = False
|
||||
return False
|
||||
|
||||
@@ -106,19 +107,22 @@ class TouchTurnStrategy:
|
||||
# For LONG (from Low): target is 38.2% up from the Low
|
||||
# For SHORT (from High): target is 38.2% down from the High
|
||||
if self.direction == -1: # LONG
|
||||
self.target_price = low + (range_size * 0.382)
|
||||
self.target_price = low + (self.range_size * 0.382)
|
||||
target_distance = self.target_price - self.entry_price
|
||||
stop_distance = target_distance / self.rr_ratio
|
||||
self.stop_loss = self.entry_price - stop_distance
|
||||
else: # SHORT
|
||||
self.target_price = high - (range_size * 0.382)
|
||||
self.target_price = high - (self.range_size * 0.382)
|
||||
target_distance = self.entry_price - self.target_price
|
||||
stop_distance = target_distance / self.rr_ratio
|
||||
self.stop_loss = self.entry_price + stop_distance
|
||||
|
||||
# 4. Calculate Percentage Move (needed for ETP scaling)
|
||||
# We use absolute percentage move relative to the entry price
|
||||
self.target_percent = (target_distance / self.entry_price) * 100
|
||||
|
||||
self.valid_setup = True
|
||||
logger.info(f"Valid Setup! Entry: {self.entry_price:.2f}, Target: {self.target_price:.2f}, SL: {self.stop_loss:.2f}")
|
||||
logger.info(f"Valid Setup! Entry: {self.entry_price:.2f}, Target: {self.target_price:.2f} ({self.target_percent:.2f}%), SL: {self.stop_loss:.2f}")
|
||||
return True
|
||||
|
||||
def get_trade_params(self):
|
||||
@@ -130,6 +134,9 @@ class TouchTurnStrategy:
|
||||
"ticker": self.ticker,
|
||||
"direction": "BUY" if self.direction == -1 else "SELL",
|
||||
"entry_price": self.entry_price,
|
||||
"current_price": self.current_price,
|
||||
"target_price": self.target_price,
|
||||
"stop_loss": self.stop_loss
|
||||
"stop_loss": self.stop_loss,
|
||||
"range_size": self.range_size,
|
||||
"target_percent": self.target_percent
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user