Compare commits

...

2 Commits

5 changed files with 96 additions and 17 deletions
+37 -14
View File
@@ -74,23 +74,46 @@ def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz):
if now.hour == 9 and now.minute >= 45:
logger.info(f"Evaluating opening candle for {yf_ticker}...")
if strategy.check_setup():
# Retry loop: wait for yfinance to publish the 09:30-09:45 candle
setup_found = False
max_retries = 12
for attempt in range(max_retries):
if strategy.check_setup():
setup_found = True
break
elif attempt < max_retries - 1:
logger.debug(f"Data not ready for {yf_ticker} yet, waiting 15s...")
time.sleep(15)
if setup_found:
params = strategy.get_trade_params()
params['ticker'] = t212_ticker
# Fetch Account Balance to calculate risk
try:
account_info = client.get_account_info()
virtual_balance = float(os.getenv("VIRTUAL_STARTING_BALANCE", 0))
if virtual_balance > 0:
risk_amount = virtual_balance * 0.01
else:
available_cash = account_info.get('cash', {}).get('availableToTrade', 1000)
risk_amount = available_cash * 0.01
except Exception as e:
logger.error(f"Failed to fetch account info for risk calculation: {e}. Defaulting to £2.50 risk.")
risk_amount = 2.50
# Anti-thundering-herd: Random jitter to prevent 429s from parallel threads
import random
time.sleep(random.uniform(0.1, 3.0))
# Fetch Account Balance to calculate risk with backoff
risk_amount = 2.50 # Fallback
for attempt in range(3):
try:
account_info = client.get_account_info()
virtual_balance = float(os.getenv("VIRTUAL_STARTING_BALANCE", 0))
if virtual_balance > 0:
risk_amount = virtual_balance * 0.01
else:
available_cash = account_info.get('cash', {}).get('availableToTrade', 1000)
risk_amount = available_cash * 0.01
break # Success
except Exception as e:
if '429' in str(e):
logger.warning(f"Rate limited on account fetch for {yf_ticker}. Retrying in {2**(attempt+1)}s...")
time.sleep(2**(attempt+1))
else:
logger.error(f"Failed to fetch account info: {e}. Defaulting to £2.50 risk.")
break
if execution.execute_trade(params, target_risk_amount=risk_amount):
# monitor_and_bracket is blocking, wait for fill (times out at 11:00)
+8
View File
@@ -0,0 +1,8 @@
import pandas as pd
import yfinance as yf
from scripts.backtest import backtest_ticker
print("Running historical check for the last 5 trading days...")
for ticker in ["NFLX", "AMZN", "AAPL"]:
res = backtest_ticker(ticker, days=5, quiet=False)
print("="*60)
+9 -3
View File
@@ -40,14 +40,20 @@ def scan_for_candidates(tickers: List[str] = DEFAULT_TICKERS, min_price: float =
df.ta.atr(length=14, append=True)
latest = df.iloc[-1]
# Safely get the close price, falling back to yesterday if today's is NaN (common at exactly 09:30)
close_price = df['Close'].iloc[-1]
if pd.isna(close_price) and len(df) > 1:
close_price = df['Close'].iloc[-2]
yesterday_atr = df['ATRr_14'].iloc[-2]
close_price = latest['Close']
# Safely get avg volume
avg_volume = df['Volume'].tail(14).mean()
if pd.isna(avg_volume):
avg_volume = 0
# Filters
if close_price < min_price or avg_volume < min_volume or pd.isna(yesterday_atr):
if pd.isna(close_price) or close_price < min_price or avg_volume < min_volume or pd.isna(yesterday_atr):
continue
atr_percent = (yesterday_atr / close_price) * 100
+6
View File
@@ -59,6 +59,12 @@ class TouchTurnStrategy:
if isinstance(intraday_data.columns, pd.MultiIndex):
intraday_data.columns = intraday_data.columns.droplevel(1)
# Timezone Correction: Convert the index to Eastern Time before filtering
if intraday_data.index.tz is None:
intraday_data.index = intraday_data.index.tz_localize('UTC').tz_convert(self.tz)
else:
intraday_data.index = intraday_data.index.tz_convert(self.tz)
# The first candle of the session (09:30)
opening_candle = intraday_data.between_time('09:30', '09:30')
if opening_candle.empty:
+36
View File
@@ -0,0 +1,36 @@
import os
import logging
from dotenv import load_dotenv
from src.api.client import Trading212Client
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def test_order():
load_dotenv()
api_key_id = os.getenv("TRADING212_API_KEY_ID")
api_key = os.getenv("TRADING212_API_KEY")
base_url = os.getenv("TRADING212_BASE_URL", "https://demo.trading212.com/api/v0/")
client = Trading212Client(api_key_id, api_key, base_url)
logger.info("Attempting to place a test Limit Order (Buy 0.1 AAPL @ $1.00)...")
try:
# We place a limit order far away from the current price so it won't fill
response = client.place_limit_order("AAPL_US_EQ", 0.1, 1.00)
logger.info(f"Success! Response: {response}")
# If successful, immediately cancel it
order_id = response.get('id')
if order_id:
logger.info(f"Cancelling test order {order_id}...")
client.cancel_order(order_id)
logger.info("Cancelled.")
except Exception as e:
logger.error(f"Failed: {e}")
# If it's a requests HTTPError, let's print the raw response text for debugging
if hasattr(e, 'response') and e.response is not None:
logger.error(f"API Response Body: {e.response.text}")
if __name__ == "__main__":
test_order()