-
Notifications
You must be signed in to change notification settings - Fork 147
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Example to Writing Algorithms / Algorithm Framework / Portfolio C…
…onstruction / Key Concepts (#2021) * add examples
- Loading branch information
1 parent
94a7bdf
commit 23ab0a8
Showing
1 changed file
with
166 additions
and
0 deletions.
There are no files selected for viewing
166 changes: 166 additions & 0 deletions
166
...orithms/34 Algorithm Framework/04 Portfolio Construction/01 Key Concepts/99 Examples.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
<p>The following examples demonstrate common practices for implementing the framework portfolio construction model.</p> | ||
|
||
<h4>Example 1: All-Weather Portfolio</h4> | ||
<p>The following algorithm uses <code>RiskParityPortfolioConstructionModel</code> to construct an all-weather portfolio that dissipates 1-year daily variance equally among ETFs that represent different asset classes, including stock market (SPY), bond (TLT), gold (GLD), oil (USO), and agricultural(DBA).</p> | ||
<div class="section-example-container"> | ||
<pre class="csharp">public class FrameworkPortfolioConstructionModelAlgorithm : QCAlgorithm | ||
{ | ||
public override void Initialize() | ||
{ | ||
SetStartDate(2024, 1, 1); | ||
SetEndDate(2024, 12, 1); | ||
|
||
// Add a universe of selected lists of ETFs that represent various asset classes. | ||
var etfs = new[] { | ||
QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA), // stock market | ||
QuantConnect.Symbol.Create("TLT", SecurityType.Equity, Market.USA), // bond | ||
QuantConnect.Symbol.Create("GLD", SecurityType.Equity, Market.USA), // gold | ||
QuantConnect.Symbol.Create("USO", SecurityType.Equity, Market.USA), // oil | ||
QuantConnect.Symbol.Create("DBA", SecurityType.Equity, Market.USA) // agricultural | ||
}; | ||
AddUniverseSelection(new ManualUniverseSelectionModel(etfs)); | ||
// Emit insights for all selected ETFs. | ||
AddAlpha(new ConstantAlphaModel(InsightType.Price, InsightDirection.Up, TimeSpan.FromDays(1))); | ||
// Using risk parity PCM to construct an all-weather portfolio to dissipate risk. | ||
SetPortfolioConstruction(new RiskParityPortfolioConstructionModel()); | ||
} | ||
}</pre> | ||
<pre class="python">class FrameworkPortfolioConstructionModelAlgorithm(QCAlgorithm): | ||
def initialize(self) -> None: | ||
self.set_start_date(2024, 1, 1) | ||
self.set_end_date(2024, 12, 1) | ||
|
||
# Add a universe of selected lists of ETFs that represent various asset classes. | ||
etfs = [ | ||
Symbol.create("SPY", SecurityType.EQUITY, Market.USA), # stock market | ||
Symbol.create("TLT", SecurityType.EQUITY, Market.USA), # bond | ||
Symbol.create("GLD", SecurityType.EQUITY, Market.USA), # gold | ||
Symbol.create("USO", SecurityType.EQUITY, Market.USA), # oil | ||
Symbol.create("DBA", SecurityType.EQUITY, Market.USA) # agricultural | ||
] | ||
self.add_universe_selection(ManualUniverseSelectionModel(etfs)) | ||
# Emit insights for all selected ETFs. | ||
self.add_alpha(ConstantAlphaModel(InsightType.PRICE, InsightDirection.UP, timedelta(1)))# Using risk parity PCM to construct an all-weather portfolio to dissipate risk. | ||
self.set_portfolio_construction(RiskParityPortfolioConstructionModel())</pre> | ||
</div> | ||
|
||
<h4>Example 2: Protective Position</h4> | ||
<p></p> | ||
<div class="section-example-container"> | ||
<pre class="csharp">public class FrameworkPortfolioConstructionModelAlgorithm : QCAlgorithm | ||
{ | ||
public override void Initialize() | ||
{ | ||
SetStartDate(2024, 5, 1); | ||
SetEndDate(2024, 7, 1); | ||
SetCash(1000000); | ||
|
||
// Set to raw data normalization for the strike and underlying price fair comparison. | ||
UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw; | ||
// Add a universe of the market representative SPY. | ||
var spy = new[] { | ||
QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA) | ||
}; | ||
AddUniverseSelection(new ManualUniverseSelectionModel(spy)); | ||
// Emit insights for all selected ETFs. Each insight lasts for 7 days for the option hedge expiry. | ||
AddAlpha(new ConstantAlphaModel(InsightType.Price, InsightDirection.Up, TimeSpan.FromDays(7))); | ||
// Using a custom PCM to control the order size and hedge option position order with a 10% OTM threshold. | ||
SetPortfolioConstruction(new ProtectivePositionPortfolioConstructionModel(0.1m)); | ||
} | ||
|
||
private class ProtectivePositionPortfolioConstructionModel : PortfolioConstructionModel | ||
{ | ||
private readonly decimal _threshold; | ||
|
||
public ProtectivePositionPortfolioConstructionModel(decimal threshold) | ||
{ | ||
_threshold = threshold; | ||
} | ||
|
||
public override List<PortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights) | ||
{ | ||
var targets = new List<PortfolioTarget>(); | ||
if (insights.Length == 0) | ||
{ | ||
return targets; | ||
} | ||
|
||
// Equally invest in each position group to dissipate the capital risk. | ||
var fundPerInsight = algorithm.Portfolio.TotalPortfolioValue / insights.Length; | ||
|
||
foreach (var insight in insights) | ||
{ | ||
var underlying = insight.Symbol; | ||
// Hedge position option right: long position should purchase OTM put, while short should purchase OTM call. | ||
var right = insight.Direction == InsightDirection.Up ? OptionRight.Put : OptionRight.Call; | ||
var threshold = insight.Direction == InsightDirection.Up ? 1m - _threshold : 1m + _threshold; | ||
|
||
// Select a protective option position that expires in 7 days. | ||
var hedge = algorithm.OptionChain(underlying) | ||
.Where(x => x.Expiry < algorithm.Time.AddDays(7) && x.Right == right) | ||
.OrderByDescending(x => x.Expiry) | ||
.ThenBy(x => Math.Abs(x.Strike - x.UnderlyingLastPrice * threshold)) | ||
.First(); | ||
// Request the protective position data for trading. | ||
var hedgeSymbol = algorithm.AddOptionContract(hedge).Symbol; | ||
|
||
// Each insight will be ordered by the position group of the underlying and the hedging option positions. | ||
var contractMultiplier = algorithm.Securities[underlying].SymbolProperties.ContractMultiplier; | ||
var quantity = Math.Floor(fundPerInsight / contractMultiplier / (hedge.BidPrice + hedge.UnderlyingLastPrice)); | ||
targets.Add(new PortfolioTarget(underlying, (int)(quantity * contractMultiplier))); | ||
targets.Add(new PortfolioTarget(hedgeSymbol, (int)quantity)); | ||
} | ||
|
||
return targets; | ||
} | ||
} | ||
}</pre> | ||
<pre class="python">class FrameworkPortfolioConstructionModelAlgorithm(QCAlgorithm): | ||
def initialize(self) -> None: | ||
self.set_start_date(2024, 5, 1) | ||
self.set_end_date(2024, 7, 1) | ||
self.set_cash(1000000) | ||
|
||
# Set to raw data normalization for a strike and the underlying price fair comparison. | ||
self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW | ||
# Add a universe of the market representative SPY. | ||
spy = [Symbol.create("SPY", SecurityType.EQUITY, Market.USA)] | ||
self.add_universe_selection(ManualUniverseSelectionModel(spy)) | ||
# Emit insights for all selected ETFs. Each insight lasts for 7 days for the option hedge expiry. | ||
self.add_alpha(ConstantAlphaModel(InsightType.PRICE, InsightDirection.UP, timedelta(7))) | ||
# Using a custom PCM to control the order size and hedge option position ordering with a 10% OTM threshold. | ||
self.set_portfolio_construction(ProtectivePositionPortfolioConstructionModel(0.1)) | ||
|
||
class ProtectivePositionPortfolioConstructionModel(PortfolioConstructionModel): | ||
def __init__(self, threshold: float) -> None: | ||
self.threshold = threshold | ||
|
||
def create_targets(self, algorithm: QCAlgorithm, insights: List[Insight]) -> List[PortfolioTarget]: | ||
targets = [] | ||
if not insights: | ||
return targets | ||
|
||
# Equally invest in each position group to dissipate the capital risk. | ||
fund_per_insight = algorithm.portfolio.total_portfolio_value / len(insights) | ||
|
||
for insight in insights: | ||
underlying = insight.symbol | ||
# Hedge position option right: long position should purchase OTM put, while short should purchase OTM call. | ||
right = OptionRight.PUT if insight.direction == InsightDirection.UP else OptionRight.CALL | ||
threshold = 1 - self.threshold if insight.direction == InsightDirection.UP else 1 + self.threshold | ||
|
||
# Select a protective option position that expires in 7 days. | ||
hedge = sorted([x for x in algorithm.option_chain(underlying) if x.expiry < algorithm.time + timedelta(7) and x.right == right], | ||
key=lambda x: (x.expiry, -abs(x.strike - x.underlying_last_price * threshold)), | ||
reverse=True)[0] | ||
# Request the protective position data for trading. | ||
hedge_symbol = algorithm.add_option_contract(hedge).symbol | ||
|
||
# Each insight will be ordered by the position group of the underlying and the hedging option positions. | ||
contract_multiplier = algorithm.securities[underlying].symbol_properties.contract_multiplier | ||
quantity = fund_per_insight // (contract_multiplier * (hedge.bid_price + hedge.underlying_last_price)) | ||
targets.append(PortfolioTarget(underlying, int(quantity * contract_multiplier))) | ||
targets.append(PortfolioTarget(hedge_symbol, int(quantity))) | ||
|
||
return targets</pre> | ||
</div> |