# Sentiment-Driven Trading with FinBERT (AAPL)
## Thesis Statement
This strategy tests whether sentiment from real-time financial news can lead to alpha (i.e. profitable, risk-adjusted trading strategy). Specifically, we leverage FinBERT—a BERT-based NLP model trained on financial texts—to quantify the tone of Apple-related news in minute-level resolution, then use this sentiment signal to decide short-term trades.
While FinBERT alone doesn’t guarantee profitability, we're looking to bridge the gap between sentiment and execution by designing a trading logic that uses sentiment shifts to make smart trades.
## Strategy Design
- Data source: Tiingo minute-level news on AAPL + FinBERT sentiment scores
- Model logic: Signal-based long/short execution with risk controls
- Validation: Backtested on 5/10–5/16, deployed on 5/17–5/20
- Risk filters: Max drawdown <15%, Sharpe Ratio >1, position weight >25%
## Assumptions
- Sentiment timing assumes fast market reaction—delayed headlines weaken edge
- Noise in minute-level sentiment may trigger false positives; we apply filters
- Strategy could underperform during news-sparse days or if headlines are contradictory
We backtested multiple variants: e.g., weighting trades by sentiment strength, filtering on momentum, and combining with moving average trend confirmation.
## Strategy - Technical Indicators
1. **Exponential Moving Averages (EMA)**
- Fast EMA: 5-minute
- Slow EMA: 15-minute
- Signal Logic:
- Bullish: EMA(5) > EMA(15)
- Bearish: EMA(5) < EMA(15)
2. **Relative Strength Index (RSI)**
- RSI Period: 9 (SMA-based)
- Signal Logic:
- Buy only when RSI < 70 (avoid entering trades when the stock is overbought)
- Sell only when RSI > 30 (avoid exiting trades when the stock is oversold)
## Strategy - FinBERT Sentiment Analysis
- Model: ProsusAI/FinBERT
- Data Source: Tiingo news descriptions mentioning AAPL
- Sentiment Score = P(Positive) - P(Negative) (range: -1 to 1)
- Rolling Window: 5 most recent sentiment scores
### Signal Threshold Logic
- Uses dynamic threshold = 0.7 × Std Dev of recent scores
- Signal Actions:
- Strong Positive (> threshold): Full Long
- Mild Positive (0.25 to threshold): Half Long
- Neutral (-0.25 to 0.25): No Position
- Mild Negative (-threshold to -0.25): Half Short
- Strong Negative (< -threshold): Full Short
*This avoids overreacting to minor sentiment swings and smooths false signals*
## Trade Execution Logic
### **AAPL Stock**
- Go Long when:
- Sentiment > threshold
- EMA(5) > EMA(15)
- RSI < 70
- Go Short when:
- Sentiment < -threshold
- EMA(5) < EMA(15)
- RSI > 30
- Exposure bounds:
- Min: 25%
- Max: 80%
### **Options (Directional + Spreads)**
- Buy Calls
- Sentiment > threshold
- EMA + RSI bullish
- IV < 90% of Historical Vol
- Buy Puts
- Sentiment < -threshold
- EMA + RSI bearish
- IV < 90% of Historical Vol
- Sell Bear Call Spread
- Sentiment < -threshold
- EMA + RSI bearish
- IV > 110% of HV
- Sell Bull Put Spread
- Sentiment > threshold
- EMA + RSI bullish
- IV > 110% of HV
## Risk Management
- Stop Loss: 5%
- Take Profit: 8%
- Max Drawdown Limit: 15%
- If breached: cut all positions by 50%
- Portfolio Exposure: 25–80% at all times
## Delta Hedging (Options Only)
- Reduce net directional exposure from options
- Net Delta = ∑(option delta × contract size × 100)
- Hedge with AAPL stock when drift > 10 shares
- Hedging frequency: Every 5 minutes
## Rebalancing & Scheduling
- Sentiment + indicators: Every minute
- Delta hedging + risk management: Every 5 minutes
- HV updates + logging: Daily after market open
- Options positions closed: 10 minutes before market close
## Backtest Result (10-Day Return)


## Bottom Line
Our strategy combines news sentiment with technical signals to trade AAPL in both stock and options. When headlines suggest strong positive or negative sentiment, we first cross-check with momentum (EMA) and strength (RSI) to confirm the trend. If all signals align, we take a position sized to our confidence level.
To manage risk in real time, we use stop-losses and take-profits to cap potential losses and lock in gains. We use Delta hedging in the options layer to neutralize unintended exposure when the market moves. We also enforce strict position size rules to avoid overexposure.
What's unique about our strategy is the use of a dynamic sentiment threshold—it adapts based on recent volatility in sentiment scores. This helps us ignore minor mood swings in the news and act only when the signal is strong and meaningful.
## QuantConnect Backtest Algorithm
```
from AlgorithmImports import *
import tensorflow as tf
from transformers import TFBertForSequenceClassification, BertTokenizer
import numpy as np
import pandas as pd
from datetime import timedelta, datetime
class EnhancedFinBERTStrategy(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2023, 5, 10)
self.SetEndDate(2023, 5, 20)
self.SetCash(1000000)
self.aapl = self.AddEquity("AAPL", Resolution.Minute)
self.aapl_symbol = self.aapl.Symbol
self.aapl_option = self.AddOption("AAPL", Resolution.Minute)
self.aapl_option.SetFilter(self.OptionFilterFunction)
self.tiingo_symbol = self.AddData(TiingoNews, self.aapl_symbol).Symbol
self.model_name = "ProsusAI/finbert"
self.tokenizer = BertTokenizer.from_pretrained(self.model_name)
self.model = TFBertForSequenceClassification.from_pretrained(self.model_name, from_pt=True)
self.sentiment_window = []
self.sentiment_window_size = 5
self.sentiment_threshold_base = 0.4
self.dynamic_threshold = self.sentiment_threshold_base
self.dynamic_threshold_factor = 0.7
self.trades_today = 0
self.min_trades_per_day = 2
self.max_trades_per_day = 5
self.daily_threshold_adjustment = 0.05
self.sentiment_cooldown = 5
self.last_sentiment_trade = datetime.min
self.current_holdings = 0
self.target_holdings = 0
self.max_position_size = 0.8
self.min_position_size = 0.5
self.active_option_positions = {}
self.last_hedge_time = datetime.min
self.hedge_frequency = timedelta(minutes=5)
self.delta_hedge_threshold = 0.1
self.max_drawdown = 0.15
self.initial_portfolio_value = self.Portfolio.TotalPortfolioValue
self.highest_portfolio_value = self.Portfolio.TotalPortfolioValue
self.stop_loss_percentage = 0.05
self.profit_target = 0.08
self.entry_price = 0
self.rsi = self.RSI(self.aapl_symbol, 9, MovingAverageType.Simple, Resolution.Minute)
self.ema_fast = self.EMA(self.aapl_symbol, 5, Resolution.Minute)
self.ema_slow = self.EMA(self.aapl_symbol, 15, Resolution.Minute)
self.historical_volatility = 0
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("AAPL", 5), self.UpdateHistoricalVolatility)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(TimeSpan.FromMinutes(5)), self.ManageRisk)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(TimeSpan.FromMinutes(3)), self.PerformDeltaHedging)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose("AAPL", 10), self.EndOfDayPositionManagement)
self.is_training_period = True
self.train_end_date = datetime(2023, 5, 16)
self.sentiment_stats = {
"positive_count": 0,
"negative_count": 0,
"neutral_count": 0,
"total_count": 0
}
self.daily_returns = []
self.previous_day_value = self.Portfolio.TotalPortfolioValue
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("AAPL", 1), self.RecordDailyMetrics)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketClose("AAPL", 0), self.AdaptiveThresholdAdjustment)
self.sharpe_ratio = 0
self.SetBenchmark(self.aapl_symbol)
self.Debug(f"Algorithm initialized: {self.Time}")
def OptionFilterFunction(self, universe):
return universe.Expiration(timedelta(days=7), timedelta(days=14)).Strikes(-5, 5)
def UpdateHistoricalVolatility(self):
history = self.History(self.aapl_symbol, 30, Resolution.Daily)
if not history.empty and len(history) >= 21:
closes = history['close'].values[-21:]
log_returns = np.diff(np.log(closes))
self.historical_volatility = np.std(log_returns) * np.sqrt(252)
self.Debug(f"Updated historical volatility: {self.historical_volatility:.4f}")
self.daily_pnl_start = self.Portfolio.TotalPortfolioValue
def OnData(self, slice: Slice) -> None:
if self.Time.date() > self.train_end_date.date():
self.is_training_period = False
if slice.ContainsKey(self.tiingo_symbol):
news = slice[self.tiingo_symbol]
if not news.description:
return
sentiment_score = self.AnalyzeSentiment(news.description)
self.sentiment_window.append(sentiment_score)
if len(self.sentiment_window) > self.sentiment_window_size:
self.sentiment_window.pop(0)
self.UpdateDynamicThreshold()
if abs(sentiment_score) > 0.3:
sentiment_type = "POSITIVE" if sentiment_score > 0 else "NEGATIVE"
self.Log(f"{self.Time} - {sentiment_type} NEWS: {news.title[:100]}... (Score: {sentiment_score:.2f})")
self.sentiment_stats["total_count"] += 1
if sentiment_score > 0:
self.sentiment_stats["positive_count"] += 1
elif sentiment_score < 0:
self.sentiment_stats["negative_count"] += 1
else:
self.sentiment_stats["neutral_count"] += 1
self.GenerateTradingSignals()
if slice.OptionChains.Count > 0:
chain = slice.OptionChains.get(self.aapl_symbol)
if chain is not None:
self.UpdateOptionDeltas(chain)
if abs(self.target_holdings - self.current_holdings) > 0.2:
time_since_last_trade = (self.Time - self.last_sentiment_trade).total_seconds() / 60
if time_since_last_trade > self.sentiment_cooldown:
self.ExecuteOptionsStrategy(chain)
def AnalyzeSentiment(self, text):
max_length = 512
text = text[:2000]
inputs = self.tokenizer(text, return_tensors="tf", max_length=max_length, truncation=True)
outputs = self.model(**inputs)
logits = outputs.logits
probabilities = tf.nn.softmax(logits, axis=-1).numpy()[0]
sentiment_score = probabilities[2] - probabilities[0]
return sentiment_score
def UpdateDynamicThreshold(self):
if len(self.sentiment_window) >= 3:
std_dev = np.std(self.sentiment_window)
self.dynamic_threshold = max(0.05, self.dynamic_threshold_factor * std_dev)
else:
self.dynamic_threshold = self.sentiment_threshold_base
def GenerateTradingSignals(self):
if len(self.sentiment_window) < 3:
return
time_since_last_trade = (self.Time - self.last_sentiment_trade).total_seconds() / 60
if time_since_last_trade < self.sentiment_cooldown:
return
avg_sentiment = sum(self.sentiment_window) / len(self.sentiment_window)
if not self.rsi.IsReady or not self.ema_fast.IsReady or not self.ema_slow.IsReady:
return
rsi_value = self.rsi.Current.Value
ema_signal = 1 if self.ema_fast.Current.Value > self.ema_slow.Current.Value else -1
signal_strength = 0
if avg_sentiment > self.dynamic_threshold and rsi_value < 70 and ema_signal == 1:
signal_strength = 1
elif avg_sentiment < -self.dynamic_threshold and rsi_value > 30 and ema_signal == -1:
signal_strength = -1
else:
if avg_sentiment > 0.25:
signal_strength = 0.5
elif avg_sentiment < -0.25:
signal_strength = -0.5
if abs(signal_strength) >= 0.5:
self.target_holdings = signal_strength
if abs(self.current_holdings - self.target_holdings) > 0.2:
self.ExecuteStockTrades()
def ExecuteStockTrades(self):
target_pct = max(min(abs(self.target_holdings) * self.max_position_size, self.max_position_size), self.min_position_size)
target_pct = target_pct * (1 if self.target_holdings > 0 else -1)
current_drawdown = (self.highest_portfolio_value - self.Portfolio.TotalPortfolioValue) / self.highest_portfolio_value
if current_drawdown > self.max_drawdown:
self.Log(f"Maximum drawdown limit reached: {current_drawdown:.2%}. Trade not executed.")
return
self.SetHoldings(self.aapl_symbol, target_pct)
self.current_holdings = self.target_holdings
self.entry_price = self.Securities[self.aapl_symbol].Price
self.last_sentiment_trade = self.Time
self.trades_today += 1
self.Log(f"Trade executed: Set AAPL holdings to {target_pct:.2%} of portfolio. Entry price: ${self.entry_price}")
def ExecuteOptionsStrategy(self, chain):
"""Execute options strategy based on sentiment signal and implied volatility"""
underlying_price = self.Securities[self.aapl_symbol].Price
atm_options = [x for x in chain if abs(x.Strike - underlying_price) < 2]
if not atm_options:
return
atm_options.sort(key=lambda x: abs(x.Strike - underlying_price))
if self.target_holdings > 0:
call_options = [x for x in atm_options if x.Right == OptionRight.Call]
if not call_options:
return
selected_call = call_options[0]
implied_vol = selected_call.ImpliedVolatility
if implied_vol < self.historical_volatility * 0.9:
option_allocation = self.Portfolio.Cash * 0.1
quantity = max(1, int(option_allocation / (selected_call.AskPrice * 100)))
self.MarketOrder(selected_call.Symbol, quantity)
self.Log(f"LONG CALL: Bought {quantity} {selected_call.Symbol} at strike ${selected_call.Strike}")
self.active_option_positions[selected_call.Symbol] = {
"position": quantity,
"delta": selected_call.Greeks.Delta,
"gamma": selected_call.Greeks.Gamma,
"entry_price": selected_call.AskPrice
}
self.trades_today += 1
elif implied_vol > self.historical_volatility * 1.1:
sell_call = call_options[0]
otm_calls = [x for x in chain if x.Right == OptionRight.Call and x.Strike > sell_call.Strike + 5]
if not otm_calls:
return
otm_calls.sort(key=lambda x: x.Strike)
buy_call = otm_calls[0]
spread_credit = sell_call.BidPrice - buy_call.AskPrice
if spread_credit <= 0:
return
max_risk = (buy_call.Strike - sell_call.Strike - spread_credit) * 100
option_allocation = self.Portfolio.Cash * 0.05
quantity = max(1, int(option_allocation / max_risk))
self.Sell(sell_call.Symbol, quantity)
self.Buy(buy_call.Symbol, quantity)
self.Log(f"BEAR CALL SPREAD: Sold {quantity} {sell_call.Symbol} at ${sell_call.Strike}, " +
f"Bought {quantity} {buy_call.Symbol} at ${buy_call.Strike}")
self.active_option_positions[sell_call.Symbol] = {
"position": -quantity,
"delta": -sell_call.Greeks.Delta,
"gamma": -sell_call.Greeks.Gamma,
"entry_price": sell_call.BidPrice
}
self.active_option_positions[buy_call.Symbol] = {
"position": quantity,
"delta": buy_call.Greeks.Delta,
"gamma": buy_call.Greeks.Gamma,
"entry_price": buy_call.AskPrice
}
self.trades_today += 1
elif self.target_holdings < 0:
put_options = [x for x in atm_options if x.Right == OptionRight.Put]
if not put_options:
return
selected_put = put_options[0]
implied_vol = selected_put.ImpliedVolatility
if implied_vol < self.historical_volatility * 0.9:
option_allocation = self.Portfolio.Cash * 0.1
quantity = max(1, int(option_allocation / (selected_put.AskPrice * 100)))
self.MarketOrder(selected_put.Symbol, quantity)
self.Log(f"LONG PUT: Bought {quantity} {selected_put.Symbol} at strike ${selected_put.Strike}")
self.active_option_positions[selected_put.Symbol] = {
"position": quantity,
"delta": selected_put.Greeks.Delta,
"gamma": selected_put.Greeks.Gamma,
"entry_price": selected_put.AskPrice
}
self.trades_today += 1
elif implied_vol > self.historical_volatility * 1.1:
sell_put = put_options[0]
otm_puts = [x for x in chain if x.Right == OptionRight.Put and x.Strike < sell_put.Strike - 5]
if not otm_puts:
return
otm_puts.sort(key=lambda x: -x.Strike)
buy_put = otm_puts[0]
spread_credit = sell_put.BidPrice - buy_put.AskPrice
if spread_credit <= 0:
return
max_risk = (sell_put.Strike - buy_put.Strike - spread_credit) * 100
option_allocation = self.Portfolio.Cash * 0.05
quantity = max(1, int(option_allocation / max_risk))
self.Sell(sell_put.Symbol, quantity)
self.Buy(buy_put.Symbol, quantity)
self.Log(f"BEAR PUT SPREAD: Sold {quantity} {sell_put.Symbol} at ${sell_put.Strike}, " +
f"Bought {quantity} {buy_put.Symbol} at ${buy_put.Strike}")
self.active_option_positions[sell_put.Symbol] = {
"position": -quantity,
"delta": -sell_put.Greeks.Delta,
"gamma": -sell_put.Greeks.Gamma,
"entry_price": sell_put.BidPrice
}
self.active_option_positions[buy_put.Symbol] = {
"position": quantity,
"delta": buy_put.Greeks.Delta,
"gamma": buy_put.Greeks.Gamma,
"entry_price": buy_put.AskPrice
}
self.trades_today += 1
def UpdateOptionDeltas(self, chain):
positions_to_remove = []
for symbol in self.active_option_positions:
contract = next((c for c in chain if c.Symbol == symbol), None)
if contract is not None:
old_delta = self.active_option_positions[symbol]["delta"]
new_delta = contract.Greeks.Delta
position_size = self.active_option_positions[symbol]["position"]
scaled_new_delta = new_delta * position_size
self.active_option_positions[symbol]["delta"] = scaled_new_delta
self.active_option_positions[symbol]["gamma"] = contract.Greeks.Gamma * position_size
entry_price = self.active_option_positions[symbol]["entry_price"]
current_price = contract.BidPrice if position_size > 0 else contract.AskPrice
price_change = (current_price - entry_price) / entry_price
if (position_size > 0 and price_change > self.profit_target) or \
(position_size > 0 and price_change < -self.stop_loss_percentage) or \
(position_size < 0 and -price_change > self.profit_target) or \
(position_size < 0 and -price_change < -self.stop_loss_percentage):
if position_size > 0:
self.Sell(symbol, abs(position_size))
outcome = "profit" if price_change > 0 else "stop-loss"
self.Log(f"Closed LONG {symbol} for {price_change:.2%} {outcome}")
else:
self.Buy(symbol, abs(position_size))
outcome = "profit" if price_change < 0 else "stop-loss"
self.Log(f"Closed SHORT {symbol} for {-price_change:.2%} {outcome}")
positions_to_remove.append(symbol)
else:
positions_to_remove.append(symbol)
for symbol in positions_to_remove:
if symbol in self.active_option_positions:
del self.active_option_positions[symbol]
def PerformDeltaHedging(self):
if not self.active_option_positions or (self.Time - self.last_hedge_time) < self.hedge_frequency:
return
net_option_delta = sum(position["delta"] for position in self.active_option_positions.values())
net_option_delta_shares = net_option_delta * 100
current_stock_position = self.Portfolio[self.aapl_symbol].Quantity
target_stock_position = -int(net_option_delta_shares)
shares_to_trade = target_stock_position - current_stock_position
if abs(shares_to_trade) > 10:
underlying_price = self.Securities[self.aapl_symbol].Price
hedge_cost = abs(shares_to_trade) * underlying_price
if hedge_cost < self.Portfolio.Cash * 0.9:
if shares_to_trade > 0:
self.Buy(self.aapl_symbol, abs(shares_to_trade))
self.Log(f"Delta Hedge: Bought {abs(shares_to_trade)} shares to offset option delta of {net_option_delta:.2f}")
else:
self.Sell(self.aapl_symbol, abs(shares_to_trade))
self.Log(f"Delta Hedge: Sold {abs(shares_to_trade)} shares to offset option delta of {net_option_delta:.2f}")
self.last_hedge_time = self.Time
def ManageRisk(self):
if not self.Portfolio.Invested:
return
if self.Portfolio.TotalPortfolioValue > self.highest_portfolio_value:
self.highest_portfolio_value = self.Portfolio.TotalPortfolioValue
current_drawdown = (self.highest_portfolio_value - self.Portfolio.TotalPortfolioValue) / self.highest_portfolio_value
if current_drawdown > self.max_drawdown:
self.Log(f"Maximum drawdown limit reached: {current_drawdown:.2%}. Reducing exposure.")
new_target = self.current_holdings * 0.5
self.SetHoldings(self.aapl_symbol, new_target)
self.current_holdings = new_target
for symbol, details in list(self.active_option_positions.items()):
position_size = details["position"]
reduction = abs(position_size) // 2
if reduction > 0:
if position_size > 0:
self.Sell(symbol, reduction)
self.Log(f"Risk reduction: Sold {reduction} of {symbol}")
self.active_option_positions[symbol]["position"] -= reduction
else:
self.Buy(symbol, reduction)
self.Log(f"Risk reduction: Bought {reduction} of {symbol}")
self.active_option_positions[symbol]["position"] += reduction
return
if self.current_holdings != 0 and self.entry_price > 0:
current_price = self.Securities[self.aapl_symbol].Price
if self.current_holdings > 0:
pnl_pct = (current_price - self.entry_price) / self.entry_price
if pnl_pct < -self.stop_loss_percentage:
self.Log(f"Stop loss triggered: {pnl_pct:.2%}. Closing position.")
self.Liquidate(self.aapl_symbol)
self.current_holdings = 0
return
if pnl_pct > self.profit_target:
self.Log(f"Profit target reached: {pnl_pct:.2%}. Taking profits.")
self.Liquidate(self.aapl_symbol)
self.current_holdings = 0
return
elif self.current_holdings < 0:
pnl_pct = (self.entry_price - current_price) / self.entry_price
if pnl_pct < -self.stop_loss_percentage:
self.Log(f"Stop loss triggered: {pnl_pct:.2%}. Closing position.")
self.Liquidate(self.aapl_symbol)
self.current_holdings = 0
return
if pnl_pct > self.profit_target:
self.Log(f"Profit target reached: {pnl_pct:.2%}. Taking profits.")
self.Liquidate(self.aapl_symbol)
self.current_holdings = 0
return
def EndOfDayPositionManagement(self):
for symbol in list(self.active_option_positions.keys()):
position_details = self.active_option_positions[symbol]
position_size = position_details["position"]
if position_size > 0:
self.Sell(symbol, abs(position_size))
self.Log(f"Closed end-of-day option position: {symbol}")
elif position_size < 0:
self.Buy(symbol, abs(position_size))
self.Log(f"Closed end-of-day option position: {symbol}")
del self.active_option_positions[symbol]
if abs(self.current_holdings) > 0:
overnight_holdings = self.current_holdings * 0.5
self.SetHoldings(self.aapl_symbol, overnight_holdings)
self.Log(f"Reduced overnight exposure to {overnight_holdings:.2%}")
self.current_holdings = overnight_holdings
def RecordDailyMetrics(self):
daily_return = (self.Portfolio.TotalPortfolioValue / self.previous_day_value) - 1
self.daily_returns.append(daily_return)
self.previous_day_value = self.Portfolio.TotalPortfolioValue
if len(self.daily_returns) > 1:
returns_array = np.array(self.daily_returns)
mean_return = np.mean(returns_array)
return_stdev = np.std(returns_array)
if return_stdev > 0:
self.sharpe_ratio = (mean_return / return_stdev) * np.sqrt(252)
self.Log(f"Daily Return: {daily_return:.2%}, Sharpe Ratio: {self.sharpe_ratio:.2f}")
self.Plot("Performance", "Daily Return", daily_return * 100)
self.Plot("Performance", "Sharpe Ratio", self.sharpe_ratio)
self.Plot("Performance", "Drawdown", (self.highest_portfolio_value - self.Portfolio.TotalPortfolioValue) / self.highest_portfolio_value * 100)
total = max(self.sentiment_stats["total_count"], 1)
self.Plot("Sentiment", "Positive %", self.sentiment_stats["positive_count"] / total * 100)
self.Plot("Sentiment", "Negative %", self.sentiment_stats["negative_count"] / total * 100)
self.Plot("Sentiment", "Neutral %", self.sentiment_stats["neutral_count"] / total * 100)
if self.active_option_positions:
net_delta = sum(pos["delta"] for pos in self.active_option_positions.values()) * 100
net_gamma = sum(pos["gamma"] for pos in self.active_option_positions.values()) * 100
self.Plot("Greeks", "Net Delta", net_delta)
self.Plot("Greeks", "Net Gamma", net_gamma)
def AdaptiveThresholdAdjustment(self):
if self.trades_today < self.min_trades_per_day:
self.dynamic_threshold = max(0.05, self.dynamic_threshold - self.daily_threshold_adjustment)
self.Log(f"[AdaptiveThresholdAdjustment] Trades today={self.trades_today} < {self.min_trades_per_day}, lower threshold to {self.dynamic_threshold:.2f}")
elif self.trades_today > self.max_trades_per_day:
self.dynamic_threshold += self.daily_threshold_adjustment
self.Log(f"[AdaptiveThresholdAdjustment] Trades today={self.trades_today} > {self.max_trades_per_day}, raise threshold to {self.dynamic_threshold:.2f}")
self.trades_today = 0
```