Compare commits

..
10 Commits
+345 -85
View File
@@ -12,6 +12,7 @@ import json
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from datetime import datetime, timedelta from datetime import datetime, timedelta
import pytz
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
import alpaca_trade_api as tradeapi import alpaca_trade_api as tradeapi
@@ -28,41 +29,42 @@ DEFAULT_CONFIG = {
"SHORT_WINDOW": 20, "SHORT_WINDOW": 20,
"LONG_WINDOW": 50, "LONG_WINDOW": 50,
"MIN_NOTIONAL": 1.0, "MIN_NOTIONAL": 1.0,
"POLL_INTERVAL": 1800, "POLL_INTERVAL": 120,
"MAX_DRAWDOWN": 0.12, "MAX_DRAWDOWN": 0.12,
"PDT_RULE": True, "PDT_RULE": True,
"USE_TRAILING_STOP": True, "USE_TRAILING_STOP": True,
"PROFIT_TARGET_1": 1.5, "PROFIT_TARGET_1": 1.5,
"PROFIT_TARGET_2": 3.0, "PROFIT_TARGET_2": 3.0,
"VOLATILITY_ADJUSTMENT": True, "VOLATILITY_ADJUSTMENT": True,
"MARKET_HOURS_FILTER": True, "MARKET_HOURS_FILTER": False,
"ENABLE_SLIPPAGE": True, "ENABLE_SLIPPAGE": True,
"SLIPPAGE_PCT": 0.0005, "SLIPPAGE_PCT": 0.0005,
"COMMISSION_PCT": 0.0005, "COMMISSION_PCT": 0.0005,
"MIN_SIGNAL_STRENGTH": 0.85, "MIN_SIGNAL_STRENGTH": 0.50,
"BACKTEST_DAYS": 90, "BACKTEST_DAYS": 90,
"USE_LIMIT_ORDERS": True, "USE_LIMIT_ORDERS": True,
"LIMIT_ORDER_TIMEOUT": 60, "LIMIT_ORDER_TIMEOUT": 60,
"ADX_THRESHOLD": 20, "ADX_THRESHOLD": 20,
"VOLUME_MULTIPLIER": 1.2, "VOLUME_MULTIPLIER": 0.5,
"ATR_STOP_MULTIPLIER": 1.5, "ATR_STOP_MULTIPLIER": 1.5,
"MAX_HOLD_TIME": 7200, "MAX_HOLD_TIME": 7200,
"REGIME_DETECTION": True, "REGIME_DETECTION": True,
"MULTIFRAME_FILTER": True, "MULTIFRAME_FILTER": False,
"BB_WINDOW": 20, "BB_WINDOW": 20,
"BB_STD": 2.0, "BB_STD": 2.0,
"USE_EMA": True, "USE_EMA": True,
"REQUIRE_CANDLE_PATTERN": True, "REQUIRE_CANDLE_PATTERN": False,
"USE_PIVOT_POINTS": True, "USE_PIVOT_POINTS": False,
"VIX_THRESHOLD": 20, "VIX_THRESHOLD": 20,
"USE_VIX_FILTER": True, "USE_VIX_FILTER": True,
"USE_FIBONACCI": True, "USE_FIBONACCI": False,
"MAX_TRADES_PER_DAY": 2, "MAX_TRADES_PER_DAY": 1000,
"SKIP_MONDAYS_FRIDAYS": True, "SKIP_MONDAYS_FRIDAYS": False,
"USE_200_SMA_FILTER": True, "USE_200_SMA_FILTER": False,
"REQUIRE_MACD_CONFIRMATION": True, "REQUIRE_MACD_CONFIRMATION": False,
"MIN_RISK_REWARD": 2.0, "MIN_RISK_REWARD": 1.5,
"PULLBACK_PERCENTAGE": 0.382 "PULLBACK_PERCENTAGE": 0.382,
"ENABLE_SHORT_SELLING": False
} }
if ENV_PATH.exists(): if ENV_PATH.exists():
@@ -127,6 +129,9 @@ USE_200_SMA_FILTER = bool(config["USE_200_SMA_FILTER"])
REQUIRE_MACD_CONFIRMATION = bool(config["REQUIRE_MACD_CONFIRMATION"]) REQUIRE_MACD_CONFIRMATION = bool(config["REQUIRE_MACD_CONFIRMATION"])
MIN_RISK_REWARD = float(config["MIN_RISK_REWARD"]) MIN_RISK_REWARD = float(config["MIN_RISK_REWARD"])
PULLBACK_PERCENTAGE = float(config["PULLBACK_PERCENTAGE"]) PULLBACK_PERCENTAGE = float(config["PULLBACK_PERCENTAGE"])
ENABLE_SHORT_SELLING = bool(config.get("ENABLE_SHORT_SELLING", False))
EASTERN = pytz.timezone('US/Eastern')
api = tradeapi.REST( api = tradeapi.REST(
os.getenv('APCA_API_KEY_ID'), os.getenv('APCA_API_KEY_ID'),
@@ -406,8 +411,8 @@ def get_vix_level():
debug_print(f"VIX from data: {vix:.2f}") debug_print(f"VIX from data: {vix:.2f}")
return vix return vix
else: else:
debug_print("No VIX data, estimating from SPY volatility...") debug_print(f"No VIX data, estimating from {SYMBOL} volatility...")
spy_bars = api.get_bars("SPY", "1Day", limit=20).df spy_bars = api.get_bars(SYMBOL, "1Day", limit=20).df
if len(spy_bars) >= 20: if len(spy_bars) >= 20:
spy_returns = spy_bars['close'].pct_change() spy_returns = spy_bars['close'].pct_change()
volatility = spy_returns.std() * np.sqrt(252) * 100 volatility = spy_returns.std() * np.sqrt(252) * 100
@@ -493,7 +498,7 @@ def should_skip_trading_day():
return False return False
today = datetime.now().weekday() today = datetime.now().weekday()
day_name = datetime.now().strftime("%A") day_name = datetime.now(EASTERN).strftime("%A")
if today == 0 or today == 4: if today == 0 or today == 4:
debug_print(f"Skipping {day_name} (skip_mondays_fridays enabled)") debug_print(f"Skipping {day_name} (skip_mondays_fridays enabled)")
return True return True
@@ -520,7 +525,17 @@ def seconds_to_human_readable(seconds):
return " ".join(time_parts) if time_parts else "0 seconds" return " ".join(time_parts) if time_parts else "0 seconds"
def format_market_time(dt_obj): def format_market_time(dt_obj):
return dt_obj.strftime("%Y-%m-%d %I:%M:%S %p %Z") if hasattr(dt_obj, 'to_pydatetime'):
dt_obj = dt_obj.to_pydatetime()
if dt_obj.tzinfo is None:
dt_obj = EASTERN.localize(dt_obj)
elif dt_obj.tzinfo != EASTERN:
dt_obj = dt_obj.astimezone(EASTERN)
eastern_time = dt_obj.strftime("%Y-%m-%d %I:%M:%S %p %Z")
local_time = dt_obj.astimezone().strftime("%I:%M%p").lstrip('0')
return f"{eastern_time} ({local_time} local)"
def apply_slippage(price, is_buy=True): def apply_slippage(price, is_buy=True):
debug_print(f"Applying slippage to price={price:.2f}, is_buy={is_buy}") debug_print(f"Applying slippage to price={price:.2f}, is_buy={is_buy}")
@@ -749,7 +764,7 @@ def advanced_signal_generator(symbol):
bars = get_recent_bars(symbol, 100) bars = get_recent_bars(symbol, 100)
if bars is None or len(bars) < 50: if bars is None or len(bars) < 50:
debug_print("Insufficient bars for signal generation") debug_print("Insufficient bars for signal generation")
return None, 0, 0 return None, 0, 0, None
debug_print(f"Received {len(bars)} bars") debug_print(f"Received {len(bars)} bars")
@@ -877,7 +892,7 @@ def advanced_signal_generator(symbol):
pullback_ok = True pullback_ok = True
debug_print(f"Pullback OK: price near short MA") debug_print(f"Pullback OK: price near short MA")
if pullback_ok and rsi > 45: if pullback_ok and rsi < 55:
debug_print(f"Pullback and RSI conditions met (RSI={rsi:.2f})") debug_print(f"Pullback and RSI conditions met (RSI={rsi:.2f})")
if hourly_trend in ['bearish', 'neutral']: if hourly_trend in ['bearish', 'neutral']:
debug_print(f"Hourly trend favorable: {hourly_trend}") debug_print(f"Hourly trend favorable: {hourly_trend}")
@@ -1003,7 +1018,8 @@ def advanced_signal_generator(symbol):
else: else:
debug_print("=== NO SIGNAL GENERATED ===") debug_print("=== NO SIGNAL GENERATED ===")
return signal, signal_strength, stop_loss position_type = 'long' if signal == 'buy' else ('short' if signal == 'sell' else None)
return signal, signal_strength, stop_loss, position_type
def wait_until_market_open(): def wait_until_market_open():
debug_print("Checking if market is open...") debug_print("Checking if market is open...")
@@ -1016,11 +1032,21 @@ def wait_until_market_open():
return return
now = clock.timestamp now = clock.timestamp
if now.tzinfo is None:
now = EASTERN.localize(now)
else:
now = now.astimezone(EASTERN)
next_open = clock.next_open next_open = clock.next_open
if next_open.tzinfo is None:
next_open = EASTERN.localize(next_open)
else:
next_open = next_open.astimezone(EASTERN)
if not clock.is_open: if not clock.is_open:
seconds_until_open = (next_open - now).total_seconds() seconds_until_open = (next_open - now).total_seconds()
debug_print(f"Market closed, {seconds_until_open:.0f} seconds until open") readable_time = seconds_to_human_readable(seconds_until_open)
debug_print(f"Market closed, {readable_time} until open")
if seconds_until_open > 0: if seconds_until_open > 0:
readable_time = seconds_to_human_readable(seconds_until_open) readable_time = seconds_to_human_readable(seconds_until_open)
logger.info(f"🕒 Market opens at {format_market_time(next_open)}") logger.info(f"🕒 Market opens at {format_market_time(next_open)}")
@@ -1031,7 +1057,7 @@ def wait_until_market_open():
time.sleep(sleep_time) time.sleep(sleep_time)
seconds_until_open -= sleep_time seconds_until_open -= sleep_time
if sleep_time >= 60: if sleep_time >= 60 and (seconds_until_open % 3600 < 60 or seconds_until_open < 3600):
remaining_readable = seconds_to_human_readable(seconds_until_open) remaining_readable = seconds_to_human_readable(seconds_until_open)
logger.info(f"⏱️ {remaining_readable} remaining...") logger.info(f"⏱️ {remaining_readable} remaining...")
debug_print(f"Waiting... {remaining_readable} remaining") debug_print(f"Waiting... {remaining_readable} remaining")
@@ -1169,10 +1195,137 @@ def submit_market_buy(symbol, notional):
debug_print(f"Market buy failed: {e}") debug_print(f"Market buy failed: {e}")
return False return False
def submit_short_sell(symbol, notional):
"""Open a short position by selling shares we don't own"""
debug_print(f"=== SUBMITTING SHORT SELL (OPENING SHORT POSITION) ===")
debug_print(f"Symbol: {symbol}, Notional: ${notional:.2f}")
try:
current_price = get_current_price(symbol)
if current_price == 0:
debug_print("Short sell failed: could not get current price")
return False
execution_price = apply_slippage(current_price, False)
shares = int(notional / execution_price)
debug_print(f"Shares to short: {shares}, Expected execution: ${execution_price:.2f}")
if shares == 0:
debug_print("Short sell failed: shares = 0")
return False
debug_print("Submitting short sell order to API...")
api.submit_order(
symbol=symbol,
qty=shares,
side="sell",
type="market",
time_in_force="day"
)
logger.info(f"🔴 SHORT SELL: {shares} shares @ ~${execution_price:.2f}")
debug_print(f"Short sell order submitted (opened short position)")
return execution_price
except Exception as e:
logger.error(f"❌ Failed short sell: {e}")
debug_print(f"Short sell failed: {e}")
return False
def submit_limit_short_sell(symbol, notional, limit_price):
debug_print(f"=== SUBMITTING LIMIT SHORT SELL (OPENING SHORT POSITION) ===")
debug_print(f"Symbol: {symbol}, Notional: ${notional:.2f}, Limit: ${limit_price:.2f}")
if notional < MIN_NOTIONAL:
logger.warning(f"⚠️ Notional ${notional:.2f} < minimum ${MIN_NOTIONAL}")
debug_print(f"Order rejected: notional too small")
return False
try:
shares = int(notional / limit_price)
debug_print(f"Calculated shares to short: {shares}")
if shares == 0:
logger.warning(f"⚠️ Cannot short fractional shares with ${notional:.2f}")
debug_print(f"Order rejected: shares = 0")
return False
debug_print(f"Submitting limit short sell order to API...")
order = api.submit_order(
symbol=symbol,
qty=shares,
side="sell",
type="limit",
limit_price=round(limit_price, 2),
time_in_force="gtc"
)
debug_print(f"Order submitted, ID: {order.id}")
logger.info(f"🔴 LIMIT SHORT SELL: {shares} shares @ ${limit_price:.2f}")
start_time = time.time()
debug_print(f"Waiting for fill (timeout: {LIMIT_ORDER_TIMEOUT}s)...")
while (time.time() - start_time) < LIMIT_ORDER_TIMEOUT:
order_status = api.get_order(order.id)
debug_print(f"Order status: {order_status.status}")
if order_status.status == 'filled':
filled_price = float(order_status.filled_avg_price)
stop_distance = abs(filled_price - limit_price) * ATR_STOP_MULTIPLIER
suggested_stop = filled_price + stop_distance
logger.info(f"✅ FILLED @ ${filled_price:.2f}")
debug_print(f"Order filled at ${filled_price:.2f}, suggested stop: ${suggested_stop:.2f}")
return filled_price
elif order_status.status in ['cancelled', 'expired', 'rejected']:
logger.warning(f"⚠️ Limit order {order_status.status}")
debug_print(f"Order {order_status.status}")
return False
time.sleep(2)
logger.warning("⏱️ Timeout - switching to market")
debug_print("Timeout reached, canceling order and switching to market")
api.cancel_order(order.id)
return submit_short_sell(symbol, notional)
except Exception as e:
logger.error(f"❌ Failed limit short sell: {e}")
debug_print(f"Limit short sell failed: {e}")
return False
def submit_buy_to_cover(symbol, qty):
"""Close a short position by buying back shares"""
debug_print(f"=== SUBMITTING BUY TO COVER (CLOSING SHORT POSITION) ===")
debug_print(f"Symbol: {symbol}, Qty: {qty}")
try:
current_price = get_current_price(symbol)
if current_price == 0:
debug_print("Buy to cover failed: could not get current price")
return False
execution_price = apply_slippage(current_price, True)
debug_print(f"Expected execution: ${execution_price:.2f}")
debug_print("Submitting buy to cover order to API...")
api.submit_order(
symbol=symbol,
qty=qty,
side="buy",
type="market",
time_in_force="day"
)
logger.info(f"🟢 BUY TO COVER: {qty} shares @ ~${execution_price:.2f}")
debug_print(f"Buy to cover order submitted (closed short position)")
return execution_price
except Exception as e:
logger.error(f"❌ Failed buy to cover: {e}")
debug_print(f"Buy to cover failed: {e}")
return False
def submit_limit_sell(symbol, qty, limit_price): def submit_limit_sell(symbol, qty, limit_price):
debug_print(f"=== SUBMITTING LIMIT SELL ORDER ===") debug_print(f"=== SUBMITTING LIMIT SELL ORDER ===")
debug_print(f"Symbol: {symbol}, Qty: {qty}, Limit: ${limit_price:.2f}") debug_print(f"Symbol: {symbol}, Qty: {qty}, Limit: ${limit_price:.2f}")
qty = abs(qty)
try: try:
debug_print("Submitting limit sell order to API...") debug_print("Submitting limit sell order to API...")
order = api.submit_order( order = api.submit_order(
@@ -1213,10 +1366,13 @@ def submit_limit_sell(symbol, qty, limit_price):
debug_print(f"Limit sell failed: {e}") debug_print(f"Limit sell failed: {e}")
return False return False
def submit_market_sell(symbol, qty): def submit_market_sell(symbol, qty):
debug_print(f"=== SUBMITTING MARKET SELL ORDER ===") debug_print(f"=== SUBMITTING MARKET SELL ORDER ===")
debug_print(f"Symbol: {symbol}, Qty: {qty}") debug_print(f"Symbol: {symbol}, Qty: {qty}")
qty = abs(qty)
try: try:
current_price = get_current_price(symbol) current_price = get_current_price(symbol)
if current_price == 0: if current_price == 0:
@@ -1242,6 +1398,7 @@ def submit_market_sell(symbol, qty):
debug_print(f"Market sell failed: {e}") debug_print(f"Market sell failed: {e}")
return False return False
def close_all_positions(): def close_all_positions():
debug_print("Closing all positions...") debug_print("Closing all positions...")
try: try:
@@ -1254,8 +1411,12 @@ def close_all_positions():
debug_print(f"Found {len(positions)} positions to close") debug_print(f"Found {len(positions)} positions to close")
logger.warning("⚠️ Closing all positions...") logger.warning("⚠️ Closing all positions...")
for pos in positions: for pos in positions:
debug_print(f"Closing position: {pos.symbol}, qty={pos.qty}") qty = int(float(pos.qty))
submit_market_sell(pos.symbol, int(float(pos.qty))) debug_print(f"Closing position: {pos.symbol}, qty={qty}")
if qty > 0:
submit_market_sell(pos.symbol, qty)
elif qty < 0:
submit_buy_to_cover(pos.symbol, abs(qty))
logger.info("✅ All positions closed") logger.info("✅ All positions closed")
debug_print("All positions closed successfully") debug_print("All positions closed successfully")
except Exception as e: except Exception as e:
@@ -1280,7 +1441,7 @@ def current_position_qty(symbol):
for pos in positions: for pos in positions:
if pos.symbol == symbol: if pos.symbol == symbol:
qty = int(float(pos.qty)) qty = int(float(pos.qty))
debug_print(f"Position qty: {qty}") debug_print(f"Position qty: {qty} ({'SHORT' if qty < 0 else 'LONG'})")
return qty return qty
debug_print("No position found") debug_print("No position found")
return 0 return 0
@@ -1335,7 +1496,7 @@ def get_market_status():
"timestamp": datetime.now() "timestamp": datetime.now()
} }
def calculate_position_size(equity, stop_loss, entry_price, regime='normal'): def calculate_position_size(equity, stop_loss, entry_price, regime='normal', max_position_pct=0.95):
debug_print(f"Calculating position size: equity=${equity:.2f}, entry=${entry_price:.2f}, stop=${stop_loss:.2f}, regime={regime}") debug_print(f"Calculating position size: equity=${equity:.2f}, entry=${entry_price:.2f}, stop=${stop_loss:.2f}, regime={regime}")
risk_amount = equity * RISK_PER_TRADE risk_amount = equity * RISK_PER_TRADE
@@ -1353,6 +1514,11 @@ def calculate_position_size(equity, stop_loss, entry_price, regime='normal'):
position_size = risk_amount / stop_distance * entry_price position_size = risk_amount / stop_distance * entry_price
position_size = max(MIN_NOTIONAL, position_size) position_size = max(MIN_NOTIONAL, position_size)
max_position = equity * max_position_pct
if position_size > max_position:
position_size = max_position
debug_print(f"Position capped at {max_position_pct:.0%} of equity: ${position_size:.2f}")
logger.info(f"💰 Position: Risk=${risk_amount:.2f}, Stop=${stop_distance:.2f}, Size=${position_size:.2f}") logger.info(f"💰 Position: Risk=${risk_amount:.2f}, Stop=${stop_distance:.2f}, Size=${position_size:.2f}")
debug_print(f"Position size: ${position_size:.2f}") debug_print(f"Position size: ${position_size:.2f}")
@@ -1363,20 +1529,20 @@ def should_trade_based_on_market_hours():
if not MARKET_HOURS_FILTER: if not MARKET_HOURS_FILTER:
debug_print("Market hours filter disabled") debug_print("Market hours filter disabled")
return True return True
now = datetime.now().time() now_eastern = datetime.now(EASTERN).time()
open_buffer_end = datetime.strptime("10:00", "%H:%M").time() open_buffer_end = datetime.strptime("10:00", "%H:%M").time()
close_buffer_start = datetime.strptime("15:30", "%H:%M").time() close_buffer_start = datetime.strptime("15:30", "%H:%M").time()
debug_print(f"Current time: {now}") debug_print(f"Current time (ET): {now_eastern}")
if now < open_buffer_end: if now_eastern < open_buffer_end:
debug_print("Before 10:00 AM, outside trading hours") debug_print("Before 10:00 AM ET, outside trading hours")
return False return False
if now >= close_buffer_start: if now_eastern >= close_buffer_start:
debug_print("After 3:30 PM, outside trading hours") debug_print("After 3:30 PM ET, outside trading hours")
return False return False
debug_print("Within trading hours") debug_print("Within trading hours")
@@ -1466,12 +1632,15 @@ def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, posit
debug_print(f"Target 1 ({PROFIT_TARGET_1}R) hit, scaling out {partial_qty} shares") debug_print(f"Target 1 ({PROFIT_TARGET_1}R) hit, scaling out {partial_qty} shares")
if partial_qty > 0: if partial_qty != 0:
if USE_LIMIT_ORDERS: if position_type == 'long':
limit_price = current_price if USE_LIMIT_ORDERS:
submit_limit_sell(symbol, partial_qty, limit_price) limit_price = current_price
submit_limit_sell(symbol, partial_qty, limit_price)
else:
submit_market_sell(symbol, partial_qty)
else: else:
submit_market_sell(symbol, partial_qty) submit_buy_to_cover(symbol, partial_qty)
logger.info(f"🎯 Target 1 ({PROFIT_TARGET_1}R) - 50% out @ ${current_price:.2f}") logger.info(f"🎯 Target 1 ({PROFIT_TARGET_1}R) - 50% out @ ${current_price:.2f}")
@@ -1482,13 +1651,16 @@ def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, posit
if profit_in_r >= PROFIT_TARGET_2: if profit_in_r >= PROFIT_TARGET_2:
remaining_qty = current_position_qty(symbol) remaining_qty = current_position_qty(symbol)
debug_print(f"Target 2 ({PROFIT_TARGET_2}R) hit, exiting {remaining_qty} shares") debug_print(f"Target 2 ({PROFIT_TARGET_2}R) hit, exiting {abs(remaining_qty)} shares")
if remaining_qty > 0: if remaining_qty != 0:
if USE_LIMIT_ORDERS: if position_type == 'long':
limit_price = current_price if USE_LIMIT_ORDERS:
submit_limit_sell(symbol, remaining_qty, limit_price) limit_price = current_price
submit_limit_sell(symbol, remaining_qty, limit_price)
else:
submit_market_sell(symbol, remaining_qty)
else: else:
submit_market_sell(symbol, remaining_qty) submit_buy_to_cover(symbol, abs(remaining_qty))
logger.info(f"🎯🎯 Target 2 ({PROFIT_TARGET_2}R) - Full exit @ ${current_price:.2f}") logger.info(f"🎯🎯 Target 2 ({PROFIT_TARGET_2}R) - Full exit @ ${current_price:.2f}")
return True return True
@@ -1589,22 +1761,38 @@ def main():
while True: while True:
debug_print("=== NEW MAIN LOOP ITERATION ===") debug_print("=== NEW MAIN LOOP ITERATION ===")
try: try:
current_date = datetime.now().date() current_date = datetime.now(EASTERN).date()
if last_reset_date != current_date: if last_reset_date != current_date:
trades_today = 0 trades_today = 0
last_reset_date = current_date last_reset_date = current_date
logger.info(f"📅 New day: {current_date}") logger.info(f"📅 New day: {current_date}")
debug_print(f"New day: {current_date}, resetting counters") debug_print(f"New day: {current_date}, resetting counters")
if hasattr(scale_out_profit_taking, 'target_1_hit'): try:
delattr(scale_out_profit_taking, 'target_1_hit')
delattr(scale_out_profit_taking, "target_1_hit")
except AttributeError:
pass
debug_print("Reset target_1_hit attribute") debug_print("Reset target_1_hit attribute")
if hasattr(atr_based_trailing_stop, 'trailing_stop'): try:
delattr(atr_based_trailing_stop, 'trailing_stop')
delattr(atr_based_trailing_stop, "trailing_stop")
except AttributeError:
pass
debug_print("Reset trailing_stop attribute") debug_print("Reset trailing_stop attribute")
if hasattr(main, 'peak_equity'):
delattr(main, 'peak_equity')
debug_print("Reset peak_equity attribute")
if should_skip_trading_day(): if should_skip_trading_day():
day_name = datetime.now().strftime("%A") day_name = datetime.now(EASTERN).strftime("%A")
logger.info(f"📅 Skipping {day_name} - monitoring mode") logger.info(f"📅 Skipping {day_name} - monitoring mode")
debug_print(f"Skipping trading today ({day_name})") debug_print(f"Skipping trading today ({day_name})")
time.sleep(3600) time.sleep(3600)
@@ -1638,8 +1826,9 @@ def main():
sma_200_trend = check_200_sma_filter(SYMBOL) sma_200_trend = check_200_sma_filter(SYMBOL)
logger.info(f"📈 200 SMA: {sma_200_trend.upper()}") logger.info(f"📈 200 SMA: {sma_200_trend.upper()}")
logger.info(f"⚙️ Config: {SYMBOL}, Risk={RISK_PER_TRADE:.2%}, Trades={trades_today}/{MAX_TRADES_PER_DAY}") short_status = "ON" if ENABLE_SHORT_SELLING else "OFF"
debug_print(f"Config: SYMBOL={SYMBOL}, RISK={RISK_PER_TRADE:.2%}, TRADES={trades_today}/{MAX_TRADES_PER_DAY}") logger.info(f"⚙️ Config: {SYMBOL}, Risk={RISK_PER_TRADE:.2%}, Trades={trades_today}/{MAX_TRADES_PER_DAY}, Shorts={short_status}")
debug_print(f"Config: SYMBOL={SYMBOL}, RISK={RISK_PER_TRADE:.2%}, TRADES={trades_today}/{MAX_TRADES_PER_DAY}, SHORT_SELLING={ENABLE_SHORT_SELLING}")
trade_count = 0 trade_count = 0
entry_price = 0 entry_price = 0
@@ -1664,14 +1853,20 @@ def main():
time.sleep(60) time.sleep(60)
continue continue
if datetime.now().date() != current_date: if datetime.now(EASTERN).date() != current_date:
logger.info("📅 Day changed - resetting") logger.info("📅 Day changed - resetting")
debug_print("Day changed, exiting session loop") debug_print("Day changed, exiting session loop")
break break
current_equity = fetch_equity() current_equity = fetch_equity()
drawdown = (opening_equity - current_equity) / opening_equity
debug_print(f"Drawdown check: opening=${opening_equity:.2f}, current=${current_equity:.2f}, drawdown={drawdown:.2%}") if not hasattr(main, 'peak_equity'):
main.peak_equity = opening_equity
if current_equity > main.peak_equity:
main.peak_equity = current_equity
drawdown = (main.peak_equity - current_equity) / main.peak_equity
debug_print(f"Drawdown check: peak=${main.peak_equity:.2f}, current=${current_equity:.2f}, drawdown={drawdown:.2%}")
if drawdown > MAX_DRAWDOWN: if drawdown > MAX_DRAWDOWN:
logger.error(f"💸 Max drawdown: {drawdown:.2%}") logger.error(f"💸 Max drawdown: {drawdown:.2%}")
@@ -1699,20 +1894,33 @@ def main():
debug_print(f"Managing active position: type={position_type}, entry=${entry_price:.2f}") debug_print(f"Managing active position: type={position_type}, entry=${entry_price:.2f}")
if entry_time: if entry_time:
time_in_trade = (datetime.now() - entry_time).total_seconds() time_in_trade = (datetime.now(EASTERN) - entry_time).total_seconds()
debug_print(f"Time in trade: {time_in_trade:.0f}s (max: {MAX_HOLD_TIME}s)") debug_print(f"Time in trade: {time_in_trade:.0f}s (max: {MAX_HOLD_TIME}s)")
if time_in_trade > MAX_HOLD_TIME: if time_in_trade > MAX_HOLD_TIME:
logger.info(f"⏰ Max hold time ({MAX_HOLD_TIME//60} min)") logger.info(f"⏰ Max hold time ({MAX_HOLD_TIME//60} min)")
debug_print(f"Max hold time exceeded, closing position") debug_print(f"Max hold time exceeded, closing position")
qty = current_position_qty(SYMBOL) qty = current_position_qty(SYMBOL)
if qty > 0: if qty != 0:
submit_market_sell(SYMBOL, qty) if position_type == 'long':
submit_market_sell(SYMBOL, qty)
else:
submit_buy_to_cover(SYMBOL, abs(qty))
position_active = False position_active = False
trade_count += 1 trade_count += 1
if hasattr(scale_out_profit_taking, 'target_1_hit'): try:
delattr(scale_out_profit_taking, 'target_1_hit')
if hasattr(atr_based_trailing_stop, 'trailing_stop'): delattr(scale_out_profit_taking, "target_1_hit")
delattr(atr_based_trailing_stop, 'trailing_stop')
except AttributeError:
pass
try:
delattr(atr_based_trailing_stop, "trailing_stop")
except AttributeError:
pass
debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit") debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit")
time.sleep(POLL_INTERVAL) time.sleep(POLL_INTERVAL)
continue continue
@@ -1721,30 +1929,56 @@ def main():
remaining_qty = current_position_qty(SYMBOL) remaining_qty = current_position_qty(SYMBOL)
if remaining_qty == 0: if remaining_qty == 0:
position_active = False position_active = False
trade_pnl = (current_price - entry_price) * 100 if position_type == 'long':
trade_pnl = (current_price - entry_price) * 100
else:
trade_pnl = (entry_price - current_price) * 100
total_pnl += trade_pnl total_pnl += trade_pnl
logger.info(f"✅ Position closed (PnL: ${trade_pnl:.2f})") logger.info(f"✅ Position closed (PnL: ${trade_pnl:.2f})")
debug_print(f"Position fully closed, PnL: ${trade_pnl:.2f}") debug_print(f"Position fully closed, PnL: ${trade_pnl:.2f}")
if hasattr(scale_out_profit_taking, 'target_1_hit'): try:
delattr(scale_out_profit_taking, 'target_1_hit')
if hasattr(atr_based_trailing_stop, 'trailing_stop'): delattr(scale_out_profit_taking, "target_1_hit")
delattr(atr_based_trailing_stop, 'trailing_stop')
except AttributeError:
pass
try:
delattr(atr_based_trailing_stop, "trailing_stop")
except AttributeError:
pass
debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit") debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit")
time.sleep(POLL_INTERVAL) time.sleep(POLL_INTERVAL)
continue continue
if atr_based_trailing_stop(SYMBOL, entry_price, current_price, stop_loss, position_type): if atr_based_trailing_stop(SYMBOL, entry_price, current_price, stop_loss, position_type):
qty = current_position_qty(SYMBOL) qty = current_position_qty(SYMBOL)
if qty > 0: if qty != 0:
submit_market_sell(SYMBOL, qty) if position_type == 'long':
submit_market_sell(SYMBOL, qty)
else:
submit_buy_to_cover(SYMBOL, abs(qty))
position_active = False position_active = False
trade_count += 1 trade_count += 1
logger.info(f"🛑 Stop hit") logger.info(f"🛑 Stop hit")
debug_print("Stop hit, position closed") debug_print("Stop hit, position closed")
if hasattr(scale_out_profit_taking, 'target_1_hit'): try:
delattr(scale_out_profit_taking, 'target_1_hit')
if hasattr(atr_based_trailing_stop, 'trailing_stop'): delattr(scale_out_profit_taking, "target_1_hit")
delattr(atr_based_trailing_stop, 'trailing_stop')
except AttributeError:
pass
try:
delattr(atr_based_trailing_stop, "trailing_stop")
except AttributeError:
pass
debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit") debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit")
time.sleep(POLL_INTERVAL) time.sleep(POLL_INTERVAL)
continue continue
@@ -1755,7 +1989,11 @@ def main():
time.sleep(POLL_INTERVAL) time.sleep(POLL_INTERVAL)
continue continue
signal, strength, signal_stop_loss = advanced_signal_generator(SYMBOL) signal, strength, signal_stop_loss, signal_position_type = advanced_signal_generator(SYMBOL)
if signal == 'sell' and not ENABLE_SHORT_SELLING:
debug_print("Short selling disabled, ignoring sell signal")
signal = None
bars = get_recent_bars(SYMBOL, 50) bars = get_recent_bars(SYMBOL, 50)
if bars is not None: if bars is not None:
@@ -1770,24 +2008,32 @@ def main():
position_size = calculate_position_size(current_equity, signal_stop_loss, current_price, regime) position_size = calculate_position_size(current_equity, signal_stop_loss, current_price, regime)
if buying_power >= position_size: if buying_power >= position_size:
if USE_LIMIT_ORDERS and signal == 'buy': execution_price = False
bid, ask = get_bid_ask(SYMBOL) if signal == 'buy':
limit_price = bid if USE_LIMIT_ORDERS:
execution_price = submit_limit_buy(SYMBOL, position_size, limit_price) bid, ask = get_bid_ask(SYMBOL)
else: limit_price = bid
execution_price = submit_market_buy(SYMBOL, position_size) execution_price = submit_limit_buy(SYMBOL, position_size, limit_price)
else:
execution_price = submit_market_buy(SYMBOL, position_size)
elif signal == 'sell':
if USE_LIMIT_ORDERS:
bid, ask = get_bid_ask(SYMBOL)
limit_price = ask
execution_price = submit_limit_short_sell(SYMBOL, position_size, limit_price)
else:
execution_price = submit_short_sell(SYMBOL, position_size)
if execution_price: if execution_price:
trade_count += 1 trade_count += 1
trades_today += 1 trades_today += 1
entry_price = execution_price entry_price = execution_price
entry_time = datetime.now() entry_time = datetime.now(EASTERN)
stop_loss = signal_stop_loss stop_loss = signal_stop_loss
position_active = True position_active = True
position_type = 'long' if signal == 'buy' else 'short' position_type = 'long' if signal == 'buy' else 'short'
risk_amount = abs(entry_price - stop_loss) / entry_price risk_amount = abs(entry_price - stop_loss) / entry_price
logger.info(f"✅ {signal.upper()} executed")
logger.info(f" Entry=${entry_price:.2f}, Stop=${stop_loss:.2f}, Risk={risk_amount:.2%}") logger.info(f" Entry=${entry_price:.2f}, Stop=${stop_loss:.2f}, Risk={risk_amount:.2%}")
logger.info(f" Regime={regime}, Strength={strength:.2f}, Trade #{trade_count} ({trades_today}/{MAX_TRADES_PER_DAY})") logger.info(f" Regime={regime}, Strength={strength:.2f}, Trade #{trade_count} ({trades_today}/{MAX_TRADES_PER_DAY})")
@@ -1801,9 +2047,22 @@ def main():
position_status = f"{position_type.upper()}" if position_active else "FLAT" position_status = f"{position_type.upper()}" if position_active else "FLAT"
try: try:
current_time = clock.timestamp.strftime("%I:%M:%S %p")
ts = clock.timestamp
if ts.tzinfo is None:
ts = EASTERN.localize(ts)
else:
ts = ts.astimezone(EASTERN)
current_time = ts.strftime("%I:%M:%S %p ET")
except: except:
current_time = datetime.now().strftime("%I:%M:%S %p")
current_time = datetime.now(EASTERN).strftime("%I:%M:%S %p ET")
hourly_trend = check_multiframe_confluence(SYMBOL) hourly_trend = check_multiframe_confluence(SYMBOL)
@@ -1853,3 +2112,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()