Compare commits
10
Commits
66f39f0690
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8621f2b791 | ||
|
|
2e63d5cca6 | ||
|
|
578d2981d8 | ||
|
|
3a2a4ef6de | ||
|
|
a6ae001e69 | ||
|
|
278c44aa9f | ||
|
|
5ab7c35773 | ||
|
|
d4b40e4a1c | ||
|
|
b1ef7ffa8a | ||
|
|
e6076bdcc9 |
No files matched your search
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
Reference in new issue
Block a user