Skip to content

Commit

Permalink
Add Example to Writing Algorithms / Algorithm Framework / Portfolio C…
Browse files Browse the repository at this point in the history
…onstruction / Key Concepts (#2021)

* add examples
  • Loading branch information
LouisSzeto authored Jan 17, 2025
1 parent 94a7bdf commit 23ab0a8
Showing 1 changed file with 166 additions and 0 deletions.
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) -&gt; 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&lt;PortfolioTarget&gt; CreateTargets(QCAlgorithm algorithm, Insight[] insights)
{
var targets = new List&lt;PortfolioTarget&gt;();
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 =&gt; x.Expiry &lt; algorithm.Time.AddDays(7) &amp;&amp; x.Right == right)
.OrderByDescending(x =&gt; x.Expiry)
.ThenBy(x =&gt; 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) -&gt; 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) -&gt; None:
self.threshold = threshold

def create_targets(self, algorithm: QCAlgorithm, insights: List[Insight]) -&gt; 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 &lt; 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>

0 comments on commit 23ab0a8

Please sign in to comment.