Allocation strategies
Built-in Allocation Strategies
Dollar Neutral
Through SelectMomentum
import pandas as pd
import tiportfolio as ti
tickers = [
"MSFT", # Technology
"NVDA", # Technology
"AMZN", # Consumer Discretionary
"JPM", # Financials
"LLY", # Healthcare
"XOM", # Energy
"COST", # Consumer Staples
"GOOGL", # Communications
"CAT", # Industrials
"PLD", # Real Estate
"INTC", # Technology
"AMD", # Technology
"ETSY", # Consumer Discretionary
"WFC", # Financials
"PFE", # Healthcare
"CVX", # Energy
"KR", # Consumer Staples
"SNAP", # Communications
"DE", # Industrials
"MPW", # Real Estate
] # Todo Fetch All Stocks from US Stock Market
data = ti.fetch_data(tickers, start="2019-01-01", end="2024-12-31")
lookback = pd.DateOffset(months=1)
lag = pd.DateOffset(days=1) # 1 day lag to avoid lookahead bias
long = ti.Portfolio(
'long',
[
ti.Select.Momentum(
n=3,
lookback=lookback,
lag=lag,
sort_descending=True,
),
ti.Weigh.Equally(),
ti.Action.Rebalance(),
],
tickers,
)
short = ti.Portfolio(
'short',
[
ti.Select.Momentum(
n=3,
lookback=lookback,
lag=lag,
sort_descending=False,
),
ti.Weigh.Equally(short=True),
ti.Action.Rebalance(),
],
tickers,
)
dollar_neutral_portfolio = ti.Portfolio(
'dollar_neutral',
[
ti.Signal.Monthly(),
ti.Select.All(), # selects child portfolio names: ["long", "short"]
ti.Weigh.Equally(), # 50% capital to each child
ti.Action.Rebalance(), # allocates capital to children
],
[long, short],
)
result = ti.run(ti.Backtest(dollar_neutral_portfolio, data))
Volatility Targeting
A volatility targeting strategy is a different animal from dollar-neutral or beta-neutral — instead of balancing longs vs. shorts, it dynamically sizes your entire position based on the portfolio's realized volatility, scaling up when markets are calm and scaling down when they get choppy.
import pandas as pd
import tiportfolio as ti
tickers = ["QQQ", "BIL", "GLD"]
data = ti.fetch_data(tickers, start="2019-01-01", end="2024-12-31")
portfolio = ti.Portfolio(
'monthly',
[
ti.Signal.Monthly(),
ti.Select.All(),
ti.Weigh.BasedOnHV(
initial_ratio={"QQQ": 0.7, "BIL": 0.2, "GLD": 0.1},
target_hv=0.60, # annualised decimal: 0.60 = 60% volatility target
lookback=pd.DateOffset(months=1),
),
ti.Action.Rebalance(),
],
tickers,
)
result = ti.run(ti.Backtest(portfolio, data))
Equal Risk Contribution (ERC)
ERC — also known as Risk Parity — sizes each asset so that every position contributes the same amount of risk to the total portfolio, rather than equal capital weight. Compared to a minimum-variance portfolio it is more diversified; compared to an equal-weight portfolio it is less volatile. It sits between the two.
Unlike Weigh.BasedOnHV (which ignores correlation) or Weigh.BasedOnBeta (which targets a single factor), ERC accounts for the full covariance structure of returns, so correlated assets naturally receive smaller allocations.
import pandas as pd
import tiportfolio as ti
tickers = ["SPY", "TLT", "GLD", "BIL"]
data = ti.fetch_data(tickers, start="2019-01-01", end="2024-12-31")
portfolio = ti.Portfolio(
'erc_monthly',
[
ti.Signal.Monthly(),
ti.Select.All(),
ti.Weigh.ERC(
lookback=pd.DateOffset(months=3), # covariance estimation window
covar_method="ledoit-wolf", # shrinkage estimator (default)
risk_parity_method="ccd", # cyclical coordinate descent (default)
maximum_iterations=100,
tolerance=1e-8,
),
ti.Action.Rebalance(),
],
tickers,
)
result = ti.run(ti.Backtest(portfolio, data))
result.plot()
result.plot_security_weights() # shows how weights shift as correlations change
The weights update every month as the covariance matrix is re-estimated. During equity sell-offs, SPY and TLT often decorrelate, so TLT's weight rises automatically — no manual regime switch needed.
Beta Neutral Strategy
If your longs have a high beta, you short more of a low-beta stock to offset it, keeping your net portfolio beta at zero.
import pandas as pd
import tiportfolio as ti
tickers = ["QQQ", "BIL", "GLD"]
data = ti.fetch_data(tickers, start="2019-01-01", end="2024-12-31")
spy_data = ti.fetch_data(["SPY"], start="2019-01-01", end="2024-12-31")
portfolio = ti.Portfolio(
'monthly',
[
ti.Signal.Monthly(),
ti.Select.All(),
ti.Weigh.BasedOnBeta(
initial_ratio={"QQQ": 0.7, "BIL": 0.2, "GLD": 0.1},
target_beta=0,
lookback=pd.DateOffset(months=1),
base_data=spy_data["SPY"], # pass the benchmark DataFrame directly
),
ti.Action.Rebalance(),
],
tickers,
)
result = ti.run(ti.Backtest(portfolio, data))