todays modifications
This commit is contained in:
@@ -19,22 +19,32 @@ os.makedirs("logs", exist_ok=True)
|
||||
log_filename = datetime.now().strftime("logs/bot_%Y-%m-%d.log")
|
||||
|
||||
# Configure logging to both console and file
|
||||
# Use a specific handler setup to enable manual flushing
|
||||
file_handler = logging.FileHandler(log_filename)
|
||||
stream_handler = logging.StreamHandler()
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [%(threadName)s] %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(log_filename),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
handlers=[file_handler, stream_handler]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Force flush helper to ensure bot logs are written to disk before thread exit
|
||||
def flush_logs():
|
||||
for handler in logging.getLogger().handlers:
|
||||
handler.flush()
|
||||
|
||||
PNL_FILE = "pnl_tracking.csv"
|
||||
|
||||
def record_pnl(ticker, direction, entry_price, exit_price, reason, pnl_r, trading_ticker=None):
|
||||
"""Appends the result of a closed trade to the PnL CSV."""
|
||||
file_exists = os.path.isfile(PNL_FILE)
|
||||
|
||||
# Safety: Fix potential 0.0 exit price in logs causing extreme PnL values
|
||||
if exit_price <= 0:
|
||||
exit_price = entry_price
|
||||
|
||||
with open(PNL_FILE, mode='a', newline='') as file:
|
||||
writer = csv.writer(file)
|
||||
if not file_exists:
|
||||
@@ -45,9 +55,14 @@ def record_pnl(ticker, direction, entry_price, exit_price, reason, pnl_r, tradin
|
||||
|
||||
label = f"{ticker} ({trading_ticker})" if trading_ticker else ticker
|
||||
logger.info(f"Recorded trade in {PNL_FILE}: {label} {direction} | Result: {reason} | PnL: {pnl_r:.2f} R")
|
||||
flush_logs()
|
||||
|
||||
def calculate_r_multiple(direction, entry_price, exit_price, stop_loss):
|
||||
"""Calculates the PnL in terms of Risk Multiples (R)."""
|
||||
# Safety: Prevent Division by Zero if SL is somehow same as entry
|
||||
if abs(entry_price - stop_loss) < 0.001:
|
||||
return 0.0
|
||||
|
||||
if direction == "BUY": # LONG
|
||||
risk = entry_price - stop_loss
|
||||
return (exit_price - entry_price) / risk if risk != 0 else 0
|
||||
@@ -55,13 +70,17 @@ def calculate_r_multiple(direction, entry_price, exit_price, stop_loss):
|
||||
risk = stop_loss - entry_price
|
||||
return (entry_price - exit_price) / risk if risk != 0 else 0
|
||||
|
||||
def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz):
|
||||
def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz, num_tickers):
|
||||
"""Handles the full strategy lifecycle for a single ticker in its own thread, then exits."""
|
||||
strategy = TouchTurnStrategy(yf_ticker)
|
||||
execution = ExecutionManager(client)
|
||||
|
||||
logger.info(f"Bot thread started for {yf_ticker} ({t212_ticker}).")
|
||||
|
||||
# Initialize variables outside the retry loop to prevent UnboundLocalError
|
||||
risk_share = 2.50 / num_tickers
|
||||
capital_share = 250.0 / num_tickers
|
||||
|
||||
try:
|
||||
now = datetime.now(tz)
|
||||
target_entry_time = now.replace(hour=9, minute=45, second=0, microsecond=0)
|
||||
@@ -97,24 +116,26 @@ def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz):
|
||||
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()
|
||||
actual_balance = float(account_info.get('totalValue', 5000.0))
|
||||
virtual_balance = max(0, actual_balance - 4750.0)
|
||||
risk_amount = virtual_balance * 0.01
|
||||
logger.info(f"Account: {actual_balance:.2f} | Virtual Balance: {virtual_balance:.2f} | Risk (1%): {risk_amount:.2f}")
|
||||
break # Success
|
||||
|
||||
risk_share = (virtual_balance * 0.01) / num_tickers
|
||||
capital_share = virtual_balance / num_tickers
|
||||
|
||||
logger.info(f"Account: {actual_balance:.2f} | Virtual: {virtual_balance:.2f} | Share: {capital_share:.2f}")
|
||||
break
|
||||
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...")
|
||||
logger.warning(f"Rate limited on account fetch for {yf_ticker}. Retrying...")
|
||||
time.sleep(2**(attempt+1))
|
||||
else:
|
||||
logger.error(f"Failed to fetch account info: {e}. Defaulting to £2.50 risk.")
|
||||
logger.error(f"Failed to fetch account info: {e}")
|
||||
break
|
||||
|
||||
if execution.execute_trade(params, target_risk_amount=risk_amount):
|
||||
if execution.execute_trade(params, target_risk_amount=risk_share, max_capital=capital_share):
|
||||
if execution.monitor_and_bracket(params):
|
||||
# Position is open, monitor for exit via SL/TP
|
||||
while datetime.now(tz).hour < 11:
|
||||
@@ -146,8 +167,7 @@ def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz):
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error in {yf_ticker} lifecycle: {e}", exc_info=True)
|
||||
finally:
|
||||
# 3. 11:00 EST - Cleanup (with jitter to prevent 429s)
|
||||
# We put this in finally to ensure it runs even on crash
|
||||
# 3. 11:00 EST - Cleanup (ensures closing even on thread crash)
|
||||
time.sleep(random.uniform(0.1, 5.0))
|
||||
|
||||
logger.info(f"Cleanup phase reached for {yf_ticker}.")
|
||||
@@ -164,6 +184,7 @@ def run_ticker_lifecycle(client, yf_ticker, t212_ticker, tz):
|
||||
execution.close_all(t212_ticker)
|
||||
|
||||
logger.info(f"Lifecycle complete for {yf_ticker}. Thread exiting.")
|
||||
flush_logs()
|
||||
|
||||
def main():
|
||||
load_dotenv()
|
||||
@@ -230,10 +251,11 @@ def main():
|
||||
logger.info(f"Final Watchlist for today: {[t['yf'] for t in final_watchlist]}")
|
||||
|
||||
threads = []
|
||||
num_active = len(final_watchlist)
|
||||
for ticker_info in final_watchlist:
|
||||
t = threading.Thread(
|
||||
target=run_ticker_lifecycle,
|
||||
args=(client, ticker_info['yf'], ticker_info['t212'], tz),
|
||||
args=(client, ticker_info['yf'], ticker_info['t212'], tz, num_active),
|
||||
name=f"Bot-{ticker_info['yf']}"
|
||||
)
|
||||
t.start()
|
||||
@@ -245,6 +267,7 @@ def main():
|
||||
t.join()
|
||||
|
||||
logger.info("All threads completed. Bot shutting down for the day.")
|
||||
flush_logs()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user