Compare commits
10
Commits
4ca5ed48b0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6556b168b | ||
|
|
8b217df705 | ||
|
|
bd7ebb33ce | ||
|
|
00aad780c8 | ||
|
|
d115873d21 | ||
|
|
fa8c13e28b | ||
|
|
45945da125 | ||
|
|
9059843460 | ||
|
|
baa41d739a | ||
|
|
caa5d76634 |
+328
-68
@@ -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}")
|
||||||
|
|
||||||
@@ -1364,19 +1530,19 @@ def should_trade_based_on_market_hours():
|
|||||||
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 position_type == 'long':
|
||||||
if USE_LIMIT_ORDERS:
|
if USE_LIMIT_ORDERS:
|
||||||
limit_price = current_price
|
limit_price = current_price
|
||||||
submit_limit_sell(symbol, partial_qty, limit_price)
|
submit_limit_sell(symbol, partial_qty, limit_price)
|
||||||
else:
|
else:
|
||||||
submit_market_sell(symbol, partial_qty)
|
submit_market_sell(symbol, partial_qty)
|
||||||
|
else:
|
||||||
|
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 position_type == 'long':
|
||||||
if USE_LIMIT_ORDERS:
|
if USE_LIMIT_ORDERS:
|
||||||
limit_price = current_price
|
limit_price = current_price
|
||||||
submit_limit_sell(symbol, remaining_qty, limit_price)
|
submit_limit_sell(symbol, remaining_qty, limit_price)
|
||||||
else:
|
else:
|
||||||
submit_market_sell(symbol, remaining_qty)
|
submit_market_sell(symbol, remaining_qty)
|
||||||
|
else:
|
||||||
|
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:
|
||||||
|
if position_type == 'long':
|
||||||
submit_market_sell(SYMBOL, qty)
|
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
|
||||||
|
if position_type == 'long':
|
||||||
trade_pnl = (current_price - entry_price) * 100
|
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:
|
||||||
|
if position_type == 'long':
|
||||||
submit_market_sell(SYMBOL, qty)
|
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
|
||||||
|
if signal == 'buy':
|
||||||
|
if USE_LIMIT_ORDERS:
|
||||||
bid, ask = get_bid_ask(SYMBOL)
|
bid, ask = get_bid_ask(SYMBOL)
|
||||||
limit_price = bid
|
limit_price = bid
|
||||||
execution_price = submit_limit_buy(SYMBOL, position_size, limit_price)
|
execution_price = submit_limit_buy(SYMBOL, position_size, limit_price)
|
||||||
else:
|
else:
|
||||||
execution_price = submit_market_buy(SYMBOL, position_size)
|
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()
|
||||||
|
|
||||||
Reference in new issue
Block a user