| Overall Statistics |
|
Total Orders 500 Average Win 2.14% Average Loss -3.40% Compounding Annual Return 24.179% Drawdown 44.800% Expectancy 0.154 Start Equity 100000.00 End Equity 301616.54 Net Profit 201.617% Sharpe Ratio 0.587 Sortino Ratio 0.635 Probabilistic Sharpe Ratio 14.104% Loss Rate 29% Win Rate 71% Profit-Loss Ratio 0.63 Alpha 0.16 Beta 0.776 Annual Standard Deviation 0.391 Annual Variance 0.153 Information Ratio 0.378 Tracking Error 0.369 Treynor Ratio 0.296 Total Fees $0.00 Estimated Strategy Capacity $28000000000.00 Lowest Capacity Asset BTCUSD 2XR Portfolio Turnover 16.25% Drawdown Recovery 636 |
#region imports
from AlgorithmImports import *
#endregion
"""
BOLLINGER BAND ALLOCATION MODEL FOR BITCOIN
This example belongs to the technical-indicator block of the strategy playground.
It extends the simplest one-asset QuantConnect models by introducing a formal
indicator: Bollinger Bands. The traded asset is BTCUSD, using daily crypto data
from the GDAX market. The purpose is pedagogical: the strategy shows how an
indicator can be used to convert price behavior into changing portfolio exposure.
The model starts by defining the backtest period, initial capital, and the crypto
subscription. It then creates a Bollinger Band indicator using a 10-day simple
moving average and a narrow band width of 0.25 standard deviations. The indicator
produces three reference levels: the upper band, the middle band, and the lower
band. These levels are used to classify the current Bitcoin price regime.
The portfolio rule is exposure-based. If Bitcoin trades above the upper band, the
model interprets the price as relatively extended and reduces exposure to 10%.
If Bitcoin is between the middle band and the upper band, the model holds a 50%
position. If Bitcoin is between the lower band and the middle band, the model
also holds a 50% position. If Bitcoin trades below the lower band, the strategy
moves to a full 100% allocation, treating the asset as relatively depressed.
The code uses an explicit if/elif/else structure. This is important because the
conditions are mutually exclusive. In the earlier version, the final else was
attached only to the immediately preceding if statement, which could cause
unexpected overwriting of portfolio targets. The improved version makes the
regime logic unambiguous.
The model also plots the strategy portfolio value, a buy-and-hold BTC reference,
the Bitcoin price, and the three Bollinger Band levels. These plots help the
reader visually connect the indicator state to the allocation decision. In the
context of the booklet, this strategy illustrates how technical indicators can
serve as a bridge between raw market data and systematic portfolio rules.
"""
class RetrospectiveYellowGreenAlligator(QCAlgorithm):
def Initialize(self):
# ------------------------------------------------------------
# 1. BACKTEST SETTINGS
# ------------------------------------------------------------
self.SetStartDate(2020, 2, 28)
self.SetEndDate(2025, 4, 1)
self._cash = 100000
self.SetCash(self._cash)
# ------------------------------------------------------------
# 2. TRADED ASSET
# ------------------------------------------------------------
self.ticker = "BTCUSD"
self.symbol = self.AddCrypto(
self.ticker,
Resolution.Daily,
Market.GDAX
).Symbol
# ------------------------------------------------------------
# 3. TECHNICAL INDICATOR
# ------------------------------------------------------------
self.bollinger_period = 10
self.bollinger_width = 0.25
self.Bolband = self.BB(
self.symbol,
self.bollinger_period,
self.bollinger_width,
MovingAverageType.Simple,
Resolution.Daily
)
self.SetWarmUp(
self.bollinger_period + 5,
Resolution.Daily
)
# ------------------------------------------------------------
# 4. BENCHMARK REFERENCE FOR PLOTTING
# ------------------------------------------------------------
history = self.History(
self.symbol,
10,
Resolution.Daily
)
if not history.empty:
self._initialValue_ticker = history["close"].iloc[0]
else:
self._initialValue_ticker = None
self.current_target_weight = 0.0
def OnData(self, data):
# ------------------------------------------------------------
# 1. DATA AND INDICATOR READINESS CHECKS
# ------------------------------------------------------------
if self.IsWarmingUp:
return
if self.symbol not in data:
return
if not self.Bolband.IsReady:
return
price = self.Securities[self.symbol].Close
if price <= 0:
return
upper_band = self.Bolband.UpperBand.Current.Value
middle_band = self.Bolband.MiddleBand.Current.Value
lower_band = self.Bolband.LowerBand.Current.Value
# ------------------------------------------------------------
# 2. PORTFOLIO CONSTRUCTION RULE
# ------------------------------------------------------------
if price > upper_band:
# Price is above the upper band:
# reduce exposure because BTC appears extended.
target_weight = 0.10
elif price > middle_band and price <= upper_band:
# Price is above the middle band but below the upper band:
# hold a moderate allocation.
target_weight = 0.50
elif price >= lower_band and price <= middle_band:
# Price is below the middle band but above the lower band:
# hold a moderate allocation.
target_weight = 0.50
else:
# Price is below the lower band:
# increase exposure because BTC appears depressed.
target_weight = 1.00
# Avoid sending repeated orders if the target has not changed.
if target_weight != self.current_target_weight:
self.SetHoldings(
self.symbol,
target_weight
)
self.current_target_weight = target_weight
# ------------------------------------------------------------
# 3. PLOTS
# ------------------------------------------------------------
if self._initialValue_ticker is not None and self._initialValue_ticker > 0:
btc_buy_hold = (
self._cash
* price
/ self._initialValue_ticker
)
self.Plot(
"Strategy Equity",
str(self.ticker),
btc_buy_hold
)
self.Plot(
"Strategy Equity",
"Portfolio Value",
self.Portfolio.TotalPortfolioValue
)
self.Plot(
"Bollinger",
"BB Lower",
lower_band
)
self.Plot(
"Bollinger",
"BB Upper",
upper_band
)
self.Plot(
"Bollinger",
"BB Middle",
middle_band
)
self.Plot(
"Bollinger",
str(self.ticker),
price
)
self.Plot(
"Portfolio State",
"Target BTC Weight",
self.current_target_weight
)