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
+4
View File
@@ -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
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}")
+14 -7
View File
@@ -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
}