Compare commits

...
10 Commits
3 changed files with 56 additions and 41 deletions

No files matched your search

+9 -9
View File
@@ -1,12 +1,12 @@
{ {
"DEBUG_MODE": true, "DEBUG_MODE": true,
"SYMBOL": "SPY", "SYMBOL": "SPY",
"BAR_TIMEFRAME": "15Min", "BAR_TIMEFRAME": "1Day",
"RISK_PER_TRADE": 0.01, "RISK_PER_TRADE": 0.01,
"SHORT_WINDOW": 20, "SHORT_WINDOW": 20,
"LONG_WINDOW": 50, "LONG_WINDOW": 50,
"MIN_NOTIONAL": 1.0, "MIN_NOTIONAL": 1.0,
"POLL_INTERVAL": 300, "POLL_INTERVAL": 3600,
"MAX_DRAWDOWN": 0.08, "MAX_DRAWDOWN": 0.08,
"PDT_RULE": true, "PDT_RULE": true,
"USE_TRAILING_STOP": true, "USE_TRAILING_STOP": true,
@@ -23,10 +23,10 @@
"LIMIT_ORDER_TIMEOUT": 60, "LIMIT_ORDER_TIMEOUT": 60,
"ADX_THRESHOLD": 25, "ADX_THRESHOLD": 25,
"VOLUME_MULTIPLIER": 0.7, "VOLUME_MULTIPLIER": 0.7,
"ATR_STOP_MULTIPLIER": 2.0, "ATR_STOP_MULTIPLIER": 2.5,
"MAX_HOLD_TIME": 10800, "MAX_HOLD_TIME": 86400,
"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,
@@ -35,15 +35,15 @@
"VIX_THRESHOLD": 30, "VIX_THRESHOLD": 30,
"USE_VIX_FILTER": false, "USE_VIX_FILTER": false,
"USE_FIBONACCI": false, "USE_FIBONACCI": false,
"MAX_TRADES_PER_DAY": 3, "MAX_TRADES_PER_DAY": 2,
"SKIP_MONDAYS_FRIDAYS": false, "SKIP_MONDAYS_FRIDAYS": false,
"USE_200_SMA_FILTER": true, "USE_200_SMA_FILTER": false,
"REQUIRE_MACD_CONFIRMATION": false, "REQUIRE_MACD_CONFIRMATION": false,
"MIN_RISK_REWARD": 2.0, "MIN_RISK_REWARD": 2.0,
"PULLBACK_PERCENTAGE": 0.382, "PULLBACK_PERCENTAGE": 0.382,
"ENABLE_SHORT_SELLING": false, "ENABLE_SHORT_SELLING": false,
"RSI_BUY_MAX": 65, "RSI_BUY_MAX": 60,
"RSI_SELL_MIN": 35, "RSI_SELL_MIN": 20,
"RSI_SELL_MAX": 70, "RSI_SELL_MAX": 70,
"RSI_RANGE_OVERSOLD": 30, "RSI_RANGE_OVERSOLD": 30,
"RSI_RANGE_OVERBOUGHT": 70, "RSI_RANGE_OVERBOUGHT": 70,
+42 -26
View File
@@ -3,7 +3,7 @@ import sys
import logging import logging
import json import json
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
import pandas as pd import pandas as pd
@@ -163,6 +163,7 @@ BAR_TIMEFRAME = config.get("BAR_TIMEFRAME", "5Min")
RISK_PER_TRADE = float(config["RISK_PER_TRADE"]) RISK_PER_TRADE = float(config["RISK_PER_TRADE"])
SHORT_WINDOW = int(config["SHORT_WINDOW"]) SHORT_WINDOW = int(config["SHORT_WINDOW"])
LONG_WINDOW = int(config["LONG_WINDOW"]) LONG_WINDOW = int(config["LONG_WINDOW"])
ENABLE_SHORT_SELLING = bool(config.get("ENABLE_SHORT_SELLING", False))
STRATEGY_MODE = config.get("STRATEGY_MODE", "ma_crossover") STRATEGY_MODE = config.get("STRATEGY_MODE", "ma_crossover")
OR_FVG_ENABLED = bool(config.get("OR_FVG_ENABLED", False)) OR_FVG_ENABLED = bool(config.get("OR_FVG_ENABLED", False))
@@ -291,7 +292,6 @@ 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))
RSI_BUY_MAX = float(config.get("RSI_BUY_MAX", 55)) RSI_BUY_MAX = float(config.get("RSI_BUY_MAX", 55))
RSI_SELL_MIN = float(config.get("RSI_SELL_MIN", 45)) RSI_SELL_MIN = float(config.get("RSI_SELL_MIN", 45))
RSI_SELL_MAX = float(config.get("RSI_SELL_MAX", 70)) RSI_SELL_MAX = float(config.get("RSI_SELL_MAX", 70))
@@ -561,8 +561,8 @@ def log_missed_signal(timestamp, signal_type, reject_reason, price_at_signal, sy
if SIGNALS_PATH.exists(): if SIGNALS_PATH.exists():
existing = pd.read_csv(SIGNALS_PATH) existing = pd.read_csv(SIGNALS_PATH)
df = pd.concat([existing, df], ignore_index=True) df = pd.concat([existing, df], ignore_index=True)
cutoff_date = datetime.now(EASTERN) - timedelta(days=30) cutoff_date = datetime.now(timezone.utc) - timedelta(days=30)
df['timestamp'] = pd.to_datetime(df['timestamp'], format='ISO8601') df['timestamp'] = pd.to_datetime(df['timestamp'], format='mixed', utc=True)
df = df[df['timestamp'] > cutoff_date] df = df[df['timestamp'] > cutoff_date]
df.to_csv(SIGNALS_PATH, index=False) df.to_csv(SIGNALS_PATH, index=False)
@@ -621,8 +621,8 @@ def log_indicators(timestamp, symbol, price, volume, rsi_val, adx_val, atr_val,
if INDICATORS_PATH.exists(): if INDICATORS_PATH.exists():
existing = pd.read_csv(INDICATORS_PATH) existing = pd.read_csv(INDICATORS_PATH)
df = pd.concat([existing, df], ignore_index=True) df = pd.concat([existing, df], ignore_index=True)
cutoff_date = datetime.now(EASTERN) - timedelta(days=7) df['timestamp'] = pd.to_datetime(df['timestamp'], format='ISO8601', utc=True)
df['timestamp'] = pd.to_datetime(df['timestamp'], format='ISO8601') cutoff_date = pd.Timestamp.now(tz='UTC') - timedelta(days=7)
df = df[df['timestamp'] > cutoff_date] df = df[df['timestamp'] > cutoff_date]
df.to_csv(INDICATORS_PATH, index=False) df.to_csv(INDICATORS_PATH, index=False)
@@ -632,7 +632,7 @@ def log_indicators(timestamp, symbol, price, volume, rsi_val, adx_val, atr_val,
def debug_print(message): def debug_print(message):
if DEBUG_MODE: if DEBUG_MODE:
debug_logger.debug(f"🔎 {message}") debug_logger.debug(f"🔎 {message}")
print(f"{datetime.now(EASTERN).strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]} - DEBUG - 🔎 {message}", flush=True) print(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]} - DEBUG - 🔎 {message}", flush=True)
def fetch_equity(): def fetch_equity():
debug_print("Fetching account equity") debug_print("Fetching account equity")
@@ -664,7 +664,9 @@ def fetch_buying_power(settlement_tracker=None):
def get_recent_bars(symbol, limit=100): def get_recent_bars(symbol, limit=100):
debug_print(f"Fetching {limit} bars for {symbol} ({BAR_TIMEFRAME})") debug_print(f"Fetching {limit} bars for {symbol} ({BAR_TIMEFRAME})")
try: try:
bars = api.get_bars(symbol, BAR_TIMEFRAME, limit=limit) buffer = int(limit * 1.5)
start = (datetime.now(EASTERN) - timedelta(days=buffer)).strftime("%Y-%m-%d")
bars = api.get_bars(symbol, BAR_TIMEFRAME, limit=limit, start=start)
if bars is None or len(bars) == 0: if bars is None or len(bars) == 0:
debug_print(f"No bars returned for {symbol}") debug_print(f"No bars returned for {symbol}")
return None return None
@@ -874,6 +876,10 @@ def calculate_position_size(equity, stop_loss, current_price):
if position_value > max_position: if position_value > max_position:
position_value = max_position position_value = max_position
debug_print(f"Position capped at 25% equity: ${position_value:.2f}") debug_print(f"Position capped at 25% equity: ${position_value:.2f}")
if position_value < MIN_NOTIONAL:
position_value = MIN_NOTIONAL
debug_print(f"Position set to minimum: ${position_value:.2f}")
debug_print(f"Calculated position size: ${position_value:.2f}")
return position_value return position_value
class ORFVGState: class ORFVGState:
@@ -916,18 +922,18 @@ def detect_fair_value_gap(bars, min_gap_pct=0.05):
if bullish_gap: if bullish_gap:
gap_size = candle_3_low - candle_1_high gap_size = candle_3_low - candle_1_high
if candle_2_high > 0: if candle_2_high > 0:
gap_pct = (gap_size / candle_2_high) * 100 gap_pct = gap_size / candle_2_high
if gap_pct >= min_gap_pct: if gap_pct >= min_gap_pct:
debug_print(f"Bullish FVG detected: gap={gap_size:.2f} ({gap_pct:.2f}%)") debug_print(f"Bullish FVG detected: gap={gap_size:.2f} ({gap_pct*100:.2f}%)")
return "bullish", i + 2 return "bullish", i + 2
bearish_gap = candle_3_high < candle_1_low bearish_gap = candle_3_high < candle_1_low
if bearish_gap: if bearish_gap:
gap_size = candle_1_low - candle_3_high gap_size = candle_1_low - candle_3_high
if candle_2_low > 0: if candle_2_low > 0:
gap_pct = (gap_size / candle_2_low) * 100 gap_pct = gap_size / candle_2_low
if gap_pct >= min_gap_pct: if gap_pct >= min_gap_pct:
debug_print(f"Bearish FVG detected: gap={gap_size:.2f} ({gap_pct:.2f}%)") debug_print(f"Bearish FVG detected: gap={gap_size:.2f} ({gap_pct*100:.2f}%)")
return "bearish", i + 2 return "bearish", i + 2
return None, None return None, None
@@ -1058,12 +1064,6 @@ def or_fvg_signal_generator(symbol):
return signal, strength, stop_loss, position_type return signal, strength, stop_loss, position_type
if position_value < MIN_NOTIONAL:
position_value = MIN_NOTIONAL
debug_print(f"Position set to minimum: ${position_value:.2f}")
debug_print(f"Calculated position size: ${position_value:.2f}")
return position_value
def advanced_signal_generator(symbol): def advanced_signal_generator(symbol):
debug_print(f"Generating signal for {symbol}") debug_print(f"Generating signal for {symbol}")
bars = get_recent_bars(symbol, BARS_FOR_SIGNAL) bars = get_recent_bars(symbol, BARS_FOR_SIGNAL)
@@ -1159,7 +1159,11 @@ def advanced_signal_generator(symbol):
stop = 0 stop = 0
position_type = None position_type = None
if regime == "trend": effective_regime = regime
if regime in ("high_vol", "low_vol"):
effective_regime = "trend"
if effective_regime == "trend":
if short_ma > long_ma and rsi_val < RSI_BUY_MAX: if short_ma > long_ma and rsi_val < RSI_BUY_MAX:
if REQUIRE_MA_CROSSOVER and not bullish_crossover: if REQUIRE_MA_CROSSOVER and not bullish_crossover:
debug_print("Bullish signal rejected: no recent crossover") debug_print("Bullish signal rejected: no recent crossover")
@@ -1188,7 +1192,7 @@ def advanced_signal_generator(symbol):
position_type = "short" position_type = "short"
debug_print(f"SELL signal: strength={strength:.2f}, stop=${stop:.2f}") debug_print(f"SELL signal: strength={strength:.2f}, stop=${stop:.2f}")
elif regime == "range": elif effective_regime == "range":
if current_price <= lower.iloc[-1] and rsi_val < RSI_RANGE_OVERSOLD: if current_price <= lower.iloc[-1] and rsi_val < RSI_RANGE_OVERSOLD:
if REQUIRE_CANDLE_PATTERN and not bullish_pattern: if REQUIRE_CANDLE_PATTERN and not bullish_pattern:
debug_print("Range buy rejected: candle pattern required") debug_print("Range buy rejected: candle pattern required")
@@ -1224,7 +1228,7 @@ def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, posit
if entry_price <= 0: if entry_price <= 0:
debug_print("Invalid entry_price, skipping scale out") debug_print("Invalid entry_price, skipping scale out")
return False return False, None
if position_type == 'long': if position_type == 'long':
profit_pct = ((current_price - entry_price) / entry_price) * 100 profit_pct = ((current_price - entry_price) / entry_price) * 100
@@ -1336,12 +1340,20 @@ def main():
while True: while True:
try: try:
clock = api.get_clock() clock = api.get_clock()
if not clock.is_open: now_et = datetime.now(EASTERN)
market_open_time = now_et.replace(hour=9, minute=30, second=0, microsecond=0)
market_close_time = now_et.replace(hour=16, minute=0, second=0, microsecond=0)
time_based_open = now_et.weekday() < 5 and market_open_time <= now_et < market_close_time
if not clock.is_open and not time_based_open:
next_open = clock.next_open.astimezone(EASTERN) next_open = clock.next_open.astimezone(EASTERN)
wait_time = (next_open - datetime.now(EASTERN)).total_seconds() wait_time = (next_open - datetime.now(EASTERN)).total_seconds()
logger.info(f"🌙 Market closed. Next open: {next_open.strftime('%I:%M %p ET on %A, %B %d')}") logger.info(f"🌙 Market closed. Next open: {next_open.strftime('%I:%M %p ET on %A, %B %d')}")
debug_print(f"Market closed, waiting {seconds_to_human_readable(int(wait_time))} until next open") debug_print(f"Market closed, waiting {seconds_to_human_readable(int(max(wait_time, 0)))} until next open")
time.sleep(min(wait_time, 3600)) while True:
remaining = (next_open - datetime.now(EASTERN)).total_seconds()
if remaining <= 0:
break
time.sleep(min(remaining, 3600))
continue continue
logger.info("🔔 Market open - session starting") logger.info("🔔 Market open - session starting")
@@ -1849,7 +1861,7 @@ def main():
pnl_pct = 0 pnl_pct = 0
status_msg += f" | PnL: {pnl_pct:+.2f}%" status_msg += f" | PnL: {pnl_pct:+.2f}%"
status_msg += f" | H:{hourly_trend} | VIX:{vix_level:.1f} | {trades_today}/{MAX_TRADES_PER_DAY}" status_msg += f" | Hourly:{hourly_trend} | VIX:{vix_level:.1f} | Trades: {trades_today}/{MAX_TRADES_PER_DAY}"
logger.info(status_msg) logger.info(status_msg)
vix_readings.append(vix_level) vix_readings.append(vix_level)
@@ -1991,7 +2003,11 @@ def main():
logger.info(f"⏰ Next session: {next_open.strftime('%Y-%m-%d %I:%M %p ET')}") logger.info(f"⏰ Next session: {next_open.strftime('%Y-%m-%d %I:%M %p ET')}")
logger.info(f"⏳ Sleeping {seconds_to_human_readable(int(wait_seconds))}") logger.info(f"⏳ Sleeping {seconds_to_human_readable(int(wait_seconds))}")
debug_print(f"Sleeping until next market open: {seconds_to_human_readable(int(wait_seconds))}") debug_print(f"Sleeping until next market open: {seconds_to_human_readable(int(wait_seconds))}")
time.sleep(wait_seconds) while True:
remaining = (next_open - datetime.now(EASTERN)).total_seconds()
if remaining <= 0:
break
time.sleep(min(remaining, 3600))
else: else:
time.sleep(60) time.sleep(60)
else: else:
+5 -6
View File
@@ -36,18 +36,16 @@ def check_macd_confirmation(bars: pd.DataFrame):
def check_200_sma_filter(symbol: str, client: AlpacaClient): def check_200_sma_filter(symbol: str, client: AlpacaClient):
daily = client.get_bars(symbol, "1Day", limit=210) daily = client.get_bars(symbol, "1Day", limit=210)
if len(daily) < 200: if len(daily) < 200:
return "neutral" return True
sma_200 = sma(daily["close"], 200).iloc[-1] sma_200 = sma(daily["close"], 200).iloc[-1]
price = daily["close"].iloc[-1] price = daily["close"].iloc[-1]
if price > sma_200 * 1.01:
return "bullish"
if price < sma_200 * 0.99: if price < sma_200 * 0.99:
return "bearish" return False
return "neutral" return True
def check_multiframe_confluence(symbol: str, use_ema: bool, client: AlpacaClient = None): def check_multiframe_confluence(symbol: str, use_ema: bool, client: AlpacaClient = None):
if client is None: if client is None:
from .engine import api as client return "neutral"
hourly = client.get_bars(symbol, "1Hour", limit=50) hourly = client.get_bars(symbol, "1Hour", limit=50)
if len(hourly) < 50: if len(hourly) < 50:
return "neutral" return "neutral"
@@ -99,3 +97,4 @@ def get_vix(client: AlpacaClient, symbol: str, use_vix_filter: bool):
logger.warning(f"Could not calculate volatility: {e}") logger.warning(f"Could not calculate volatility: {e}")
logger.warning("VIX data unavailable, skipping VIX filter for this iteration") logger.warning("VIX data unavailable, skipping VIX filter for this iteration")
return 0 return 0