fix: resolve 429 rate limit errors with API backoff and correct timezone handling for 09:30 candle
This commit is contained in:
@@ -75,7 +75,7 @@ def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz):
|
|||||||
if now.hour == 9 and now.minute >= 45:
|
if now.hour == 9 and now.minute >= 45:
|
||||||
logger.info(f"Evaluating opening candle for {yf_ticker}...")
|
logger.info(f"Evaluating opening candle for {yf_ticker}...")
|
||||||
|
|
||||||
# Retry loop: wait for yfinance to publish the 09:30-09:45 candle (up to 3 minutes)
|
# Retry loop: wait for yfinance to publish the 09:30-09:45 candle
|
||||||
setup_found = False
|
setup_found = False
|
||||||
max_retries = 12
|
max_retries = 12
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
@@ -90,19 +90,30 @@ def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz):
|
|||||||
params = strategy.get_trade_params()
|
params = strategy.get_trade_params()
|
||||||
params['ticker'] = t212_ticker
|
params['ticker'] = t212_ticker
|
||||||
|
|
||||||
# Fetch Account Balance to calculate risk
|
# Anti-thundering-herd: Random jitter to prevent 429s from parallel threads
|
||||||
try:
|
import random
|
||||||
account_info = client.get_account_info()
|
time.sleep(random.uniform(0.1, 3.0))
|
||||||
virtual_balance = float(os.getenv("VIRTUAL_STARTING_BALANCE", 0))
|
|
||||||
|
# Fetch Account Balance to calculate risk with backoff
|
||||||
if virtual_balance > 0:
|
risk_amount = 2.50 # Fallback
|
||||||
risk_amount = virtual_balance * 0.01
|
for attempt in range(3):
|
||||||
else:
|
try:
|
||||||
available_cash = account_info.get('cash', {}).get('availableToTrade', 1000)
|
account_info = client.get_account_info()
|
||||||
risk_amount = available_cash * 0.01
|
virtual_balance = float(os.getenv("VIRTUAL_STARTING_BALANCE", 0))
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to fetch account info for risk calculation: {e}. Defaulting to £2.50 risk.")
|
if virtual_balance > 0:
|
||||||
risk_amount = 2.50
|
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):
|
if execution.execute_trade(params, target_risk_amount=risk_amount):
|
||||||
# monitor_and_bracket is blocking, wait for fill (times out at 11:00)
|
# monitor_and_bracket is blocking, wait for fill (times out at 11:00)
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -59,6 +59,12 @@ class TouchTurnStrategy:
|
|||||||
if isinstance(intraday_data.columns, pd.MultiIndex):
|
if isinstance(intraday_data.columns, pd.MultiIndex):
|
||||||
intraday_data.columns = intraday_data.columns.droplevel(1)
|
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)
|
# The first candle of the session (09:30)
|
||||||
opening_candle = intraday_data.between_time('09:30', '09:30')
|
opening_candle = intraday_data.between_time('09:30', '09:30')
|
||||||
if opening_candle.empty:
|
if opening_candle.empty:
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user