Skip to content

Portfolio construction and risk · Intermediate

Building strategies from components

Self-contained components with their own signal, sizing and state, tested alone and in the book, and why a component’s standalone Sharpe ratio says little about what it adds.

20 min read8 referencesIntroduction to the series

Educational material only. Not investment advice.

Contents
  1. 01What a component is
  2. 02Signal, target, sizing, and the book
  3. 03State machines and restarts
  4. 04Testing a component alone
  5. 05An interactive laboratory
  6. 06Measuring a contribution
  7. 07A checklist
  8. —References

Abstract

We build strategies from components: small units with their own signal, sizing and state that emit target weights through one interface, while budgets, netting and book-wide limits live outside them. Components can be tested alone for invariants, look-ahead and restart behaviour; a restart test shows that a no-trade band makes a seemingly stateless rule hard to recover from a short history. On a century of public data, three textbook components show why a contribution must be measured alone, inside the book and by leaving it out at equal risk: the three answers disagree, and only together do they show what each component adds and what it costs.

Key takeaways

  • A component reads data up to a close and emits target weights per instrument; it owns its state and shares nothing mutable.
  • Keep signal, target and sizing apart inside a component, and budgets, netting, costs and book-wide limits outside it.
  • Draw every state machine and test restarts: a no-trade band can make a rule that looks stateless impossible to re-derive from a short history, so persist what it holds.
  • Test causality by cutting the history at a date and by replacing what follows with extreme returns, comparing every earlier target; a cut alone misses most look-ahead.
  • Measure a contribution alone, inside the book and by leaving it out at equal risk, with its uncertainty; standalone quality says little about what a component adds.

Before you start

  • Indicators and the 200-day moving average, for the trend rule
  • Volatility targeting, for the sizing rule
  • Covariance and portfolio variance, for risk contributions
  • Reading Python classes and pandas

A strategy that grows by accretion ends as one long function in which every rule can see every other rule’s variables, and no rule can be tested or replaced on its own. We build strategies from components instead: small units with their own signal, sizing and state, joined to the book through one narrow interface. (The book is everything the account holds: the sum of what its components ask for.) Each component can then be tested alone and in the book, and its contribution measured. On a century of public data, what a component adds to a book turns out to differ sharply from how good it looks alone.

What a component is

A component, often called a sleeve in the industry, is a self-contained unit that reads market data up to the close of day t and emits target weights, one per instrument, to be held from day t + 1. It owns its parameters and state, shares nothing mutable with other components, and knows nothing of capital, orders or the broker. Everything a component decides is visible in its output, and the rest of the system consumes only that output. Parnas argued in 1972 that a system should be divided along the decisions most likely to change, each hidden inside one module behind a stable interface.1 In a trading system the decisions most likely to change are signals and sizing rules, so those are what a component hides.

The same separation runs through the practitioner literature. Grinold and Kahn separate the forecast of returns from the construction of the portfolio that acts on it, and Carver builds a trading system from independent “subsystems” whose forecasts are sized and combined in separate, testable steps.2,3 LEAN’s algorithm framework, described in QuantConnect and LEAN, makes it an engine feature: alpha models emit insights, a portfolio-construction model turns them into targets, a risk model adjusts the targets and an execution model trades them, and the models are meant not to talk to each other.4 A component maps naturally to an alpha model emitting one insight per instrument (or to a plain class the algorithm calls at the close), the aggregator to a portfolio-construction model, the overlay below to a risk-management model, and the component’s warm-up to the algorithm’s warm-up period.

site_research/fieldnotes/components.py
class Component(Protocol):
    name: str
    warm_up: int                                  # sessions of history before the first valid target

    def targets(self, data: pd.DataFrame) -> pd.DataFrame:
        """Target weight per instrument, decided at each close from rows up to that close."""


class Trend:
    """Hold the market while its total-return index is above its moving average: a state machine
    (warming up, risk off, risk on) with a hysteresis band, so it switches only on a clear cross.
    While warming up it holds nothing and the capital earns T-bills."""
    lookback: int = 200
    band: float = 0.02
    name: str = "trend"

    @property
    def warm_up(self) -> int:
        return self.lookback

    def targets(self, data: pd.DataFrame) -> pd.DataFrame:
        price = (1.0 + data["mkt"] + data["rf"]).cumprod()
        state = indicators.sma_regime(price, self.lookback, self.band)
        return pd.DataFrame({"mkt": state, "mom": 0.0}, index=data.index)


class VolTarget:
    """Hold one instrument at a weight of target / forecast volatility, capped, re-traded only
    outside a no-trade band (the rule of the volatility-targeting note)."""
    instrument: str = "mkt"
    target: float = 0.10
    span: int = 60
    cap: float = 1.5
    band: float = 0.10
    name: str = "vol_market"

    @property
    def warm_up(self) -> int:
        return 20

    def targets(self, data: pd.DataFrame) -> pd.DataFrame:
        r = data[self.instrument] + (data["rf"] if self.instrument == "mkt" else 0.0)
        w = voltarget.target_weights(voltarget.ewma_vol(r, self.span), self.target, self.cap, self.band)
        out = pd.DataFrame(0.0, index=data.index, columns=list(INSTRUMENTS))
        out[self.instrument] = w
        return out
The interface and two textbook components. Trend reuses the regime rule of Indicators and the 200-day moving average, and VolTarget the sizing rule of Volatility targeting; neither knows the other exists.

Our three example components trade two public return streams from the Kenneth R. French Data Library: the US stock market in excess of Treasury bills, and the long-short momentum factor.5 The first holds the market while its total-return index is above its 200-day average, the rule tested in Indicators and the 200-day moving average, here with a ±2% hysteresis band. The second holds the market at a weight of 10% divided by its forecast volatility, capped at 1.5, the rule of Volatility targeting with a lower target and cap; its no-trade band leaves the weight alone until the target moves by more than 10% of the weight held. The third applies the same sizing to momentum, where Barroso and Santa-Clara found that it nearly doubles the Sharpe ratio and removes most of the strategy’s crashes.6 They are chosen to be simple and well documented, and two of them deliberately trade the same instrument. Because of the band, the 5 bp charged on each unit traded and a later start (3 September 1927, after 252 sessions of momentum history), and because the sizing uses a 10% target and a 1.5× cap where Volatility targeting uses 12% and 2×, their stand-alone Sharpe ratios below differ slightly from the figures in those two notes.

Signal, target, sizing, and the book

Inside a component the work falls into three steps, and it pays to keep them apart even there. The signal is a measurement: the ratio of a price to its average, a volatility forecast. The target is a decision about direction and eligibility: risk on or off, long or flat. Sizing turns the target into a weight. Outside the components, the aggregator applies capital budgets and nets instruments that several components trade; an overlay applies limits that belong to the whole book, here a cap on gross exposure; and execution trades the difference between the book’s target and what it holds.

Figure 1Components, aggregator, overlay, execution
Three components, each turning data into target weights through its own signal, target and sizing, cross the interface into an aggregator that applies budgets and nets shared instruments; a book-level overlay caps gross exposure; execution trades the difference between the book’s target and its holdings.each: signal → target → size, own stateTrendmarket above its 200-day averageVolatility targetmarket, 10% targetVolatility targetmomentum, 10% targetAggregatorbudgets, nettingOverlaygross exposure capExecutiontrade the differenceinterface: a weight per instrument, decided at a close
The dashed line is the interface: a weight per instrument, decided at a close. Everything to its right belongs to the book, and no component can see it.

Two rules keep the layers apart. Costs are charged on what the book trades, after netting, never on what each component would trade alone, because two components that move the same instrument in opposite directions on the same day cost nothing between them. And limits that concern the whole book, such as gross exposure or margin, live in the overlay, where they scale every component in proportion, instead of in each component, where they would interact in ways nobody designed. In our example the overlay’s cap of 2× never binds, because the book’s gross exposure peaks at 1.33×; set it to 1× in the lab below to watch it work.

site_research/fieldnotes/components.py
def book_weights(parts: dict[str, pd.DataFrame], budgets: dict[str, float], gross_cap: float = 2.0):
    """The aggregator: the budget-weighted sum of the components' targets, so shared instruments
    net, then one book-level limit that scales everything down when gross exposure exceeds
    `gross_cap`. Returns the book's weights and the scale applied each day."""
    raw = sum(budgets[k] * parts[k] for k in parts)
    gross = raw.abs().sum(axis=1)
    scale = pd.Series(np.where(gross > gross_cap, gross_cap / gross.where(gross > 0, 1.0), 1.0), index=gross.index)
    return raw.mul(scale, axis=0), scale


def book_returns(data: pd.DataFrame, weights: pd.DataFrame, cost: float = 0.0005) -> pd.DataFrame:
    """Daily P&L of the book: capital earns T-bills, each weight (decided at the previous close)
    earns its instrument's excess return, and costs are charged on what the BOOK trades."""
    w = weights.shift(1).fillna(0.0)
    traded = w.diff().fillna(w).abs().sum(axis=1)
    excess = (w * data[list(INSTRUMENTS)]).sum(axis=1)
    return pd.DataFrame({"ret": data["rf"] + excess - cost * traded, "excess": excess, "traded": traded})

State machines and restarts

Many signals are simple state machines, and it helps to draw them. The trend component has three states: warming up, until two hundred closes exist; risk off; and risk on. With a hysteresis band it switches on only when the close clears the average by the band and off only when it falls below by the band, and in between it keeps its state. That makes the state path-dependent: the position today depends on where the price has been, as well as where it is.

Figure 2The trend component’s states
The trend component as a state machine: warming up for 200 sessions, then risk off or risk on; it switches on only when the close rises above the average by the band, and off only when it falls below it by the band.Warming upweight 0, earns T-billsRisk offweight 0, earns T-billsRisk onweight 1 in the market200 closes seenclose > average × (1 + band)close < average × (1 − band)inside the band: stayinside the band: stay
Warm-up is a state in its own right. Here it holds nothing and the capital earns T-bills; what it must never do is hold a position computed from inputs that do not yet exist. Inside the band, the state is whatever it was yesterday.

State matters most on the day a system restarts. A component that can re-derive its state from recent history needs nothing saved; one that cannot must persist its state and prove, on restart, that the saved state is still right. The question can be tested directly: restart the component from a limited window of history on many dates and compare its targets with those of a copy that has run from the beginning. We did this on 200 random dates from 13 May 1953, after the end of Saturday trading, so that 260 sessions are about a calendar year.

Component260 sessions520 sessions1,300 sessions
Trend, ±2% band100% / 100%100% / 100%100% / 100%
Vol-targeted market, no-trade band3% / 72%92% / 96%100% / 100%
Vol-targeted market, no band12% / 100%100% / 100%100% / 100%
Vol-targeted momentum, no-trade band4% / 65%80% / 91%100% / 100%
Vol-targeted momentum, no band43% / 100%100% / 100%100% / 100%

Share of 200 random dates on which a restart that reads only the given history reproduces the target weight of a copy that has run since the data begin in 1926: exactly (to within 0.000001), then to within 0.01 of weight.

The trend rule, whose state looks path-dependent, is re-derived exactly from a year of history, because the price leaves a 2% band often enough to reset the state. The volatility target looks stateless, a function of recent returns. Yet with its no-trade band, a restart from two years of history still misses the held weight by more than 0.01 on 5% of dates for the market and 9% for momentum. The band makes the held weight depend on when the component last traded, which a short history cannot know. Without the band, a year of history reproduces the weight to within 0.01 on every date and two years reproduce it exactly; the small differences at one year are the estimator’s starting value, which decays by a factor of about three thousand in a year. The practical rule: persist what a component holds and the state it carries (for the volatility target, the weight held and the variance estimate; for trend, the regime), and on restart replay enough history to check that the replayed state agrees with the saved one. LEAN’s warm-up performs that replay on deployment.7

Figure 3Restart a component
Component
No-trade band
History the restart reads

On the restart day the restarted copy holds 0.874× against 0.824× for the copy that never stopped, a difference of 6.1% of the position. The two differed by more than 0.01 of weight on the restart day and all 260 sessions after it.

history the restart readsafter the restart0.0×0.5×1.0×1.5×2003-072004-082005-08
Running since the data beginRestarted with 260 sessions of historyRestart day
Kenneth R. French Data Library, daily. The shaded span is the history the restarted copy reads; the grey line is the weight of a copy that has run since 1926, the dashed line the restarted copy’s, through the restart day and the year after. It opens on a restart in August 2004 that reads a year of history and holds a different weight from the copy that never stopped for the whole of the following year. Suggested experiments: remove the band and watch the two agree; give the restart two years of history; slide the date to find other restarts that go wrong; then restart trend and find that it never disagrees.
site_research/fieldnotes/components.py
def restart_agreement(c: Component, data: pd.DataFrame, history: int, dates, atol: float = 1e-6) -> float:
    """Share of dates on which a restart that sees only the last `history` sessions reproduces,
    to within `atol`, the targets of a component that has run from the start. Below one, the
    state must be persisted (or replayed from the beginning), because it cannot be re-derived."""
    full = c.targets(data)
    same = []
    for d in dates:
        i = data.index.get_loc(d)
        part = c.targets(data.iloc[max(0, i + 1 - history): i + 1]).iloc[-1]
        same.append(np.allclose(part.to_numpy(), full.loc[d].to_numpy(), rtol=0, atol=atol))
    return float(np.mean(same))

Testing a component alone

A component with one interface can be tested like any other function, before any backtest is judged. Two kinds of test catch the commonest errors. Invariants are properties every output must have whatever the logic: the right instruments, aligned dates, finite values, weights inside a sanity bound. Causality is the property a backtest cannot check by itself: no target up to day t may change when the history after t changes. We test it twice on each date, by cutting the history at t and by replacing every row after t with an extreme return, and compare every target up to t.

site_research/fieldnotes/components.py
def check_targets(c: Component, t: pd.DataFrame, data: pd.DataFrame, max_abs: float = 3.0) -> None:
    """Invariants every component's output must satisfy, whatever its logic."""
    assert list(t.columns) == list(INSTRUMENTS), f"{c.name}: unknown instruments {list(t.columns)}"
    assert t.index.equals(data.index), f"{c.name}: targets not aligned to the data"
    assert np.isfinite(t.to_numpy()).all(), f"{c.name}: a target is NaN or infinite"
    assert (t.abs() <= max_abs).all().all(), f"{c.name}: a target exceeds {max_abs}"


def check_causal(c: Component, data: pd.DataFrame, dates, shocks=(-0.5, 1.0)) -> None:
    """No look-ahead: every target up to day t must be unchanged when the history is cut at t,
    and when every later row is replaced by an extreme return. Comparing the whole prefix under a
    large shock catches a peek on any day, not only on the rare days where it changes the answer."""
    full = c.targets(data)
    for d in dates:
        cut = c.targets(data.loc[:d])
        assert np.allclose(cut.to_numpy(), full.loc[:d].to_numpy(), atol=1e-12), f"{c.name} looks ahead at {d} (cut)"
        for k in shocks:
            alt = data.copy()
            alt.loc[alt.index > d, list(INSTRUMENTS)] = k
            with np.errstate(over="ignore"):                   # a shocked future may overflow; the past must not change
                got = c.targets(alt).loc[:d]
            assert np.allclose(got.to_numpy(), full.loc[:d].to_numpy(), atol=1e-12), f"{c.name} looks ahead at {d} (shock {k})"
The build runs both checks on every component, on 12 random dates, before any result is computed.

Cutting the history is not enough on its own, and the build proves it with a deliberately broken copy of the trend component that reads the next day’s close.

Step by stepCatching a component that peeks
Market total-return index, start of window = 11.001.101.20MayJunJulAugSeptOctHonestPeekingTest date, 29 Jul 19395 Sep 1939: +8.8%Cut: nothing after−50% every day✓ both unchanged to here✓ honest unchanged✗ peek changed: caught
  1. Step 1 of 4

    An honest component

    The trend component decides at each close from the closes up to that one, and the target it sets is held from the next day. It switches only when the market’s index clears its 200-day average by the 2% band, as it did at the close of 5 September 1939.

  2. Step 2 of 4

    A one-day peek

    The broken copy reads the next day’s close, so whenever the rule switches, it switches a day early. On 5 September 1939, the first session after Britain and France declared war on Germany, the market rose 8.8%, and the copy was already in. Over the whole history the peek lifts the Sharpe ratio from 0.68 to 0.93.

  3. Step 3 of 4

    Cut the history

    The obvious test cuts the history at a date, here 29 July 1939, reruns the component and checks that no target up to that date has changed. Both rules pass: with nothing after the date, the peeking copy reads that day’s own close in place of the next, which matters only on the rare days when one of those two closes crosses the band and the other does not. Run on every date after the warm-up, this check catches the peek on only 152 of 26,016 dates (0.6%).

  4. Step 4 of 4

    Shock the future

    The second half of the test replaces every day after the test date with an extreme return, first a crash of 50% a day and then a doubling, and compares every target up to the date each time. The honest targets do not move. The peeking copy’s target on the test date reads the crash and drops to T-bills, so the test catches it, as it did on the first of the build’s 12 dates.

Kenneth R. French Data Library, daily: the market’s total-return index, the trend component of this note and its peeking copy, computed in the browser with the laboratory’s port of components.py. The window is the 160 sessions around the day the peek gained most over the honest rule, and the test date is 30 sessions before that day. Without script, or with reduced motion, the figure shows its final state.

site_research/fieldnotes/components.py
def check_consumed(parts: dict[str, pd.DataFrame], budgets: dict[str, float], gross_cap: float = 2.0) -> None:
    """The consumer test: switching any component off must change the book. A component that
    passes every test of its own and is then ignored by the aggregator fails here."""
    book, _ = book_weights(parts, budgets, gross_cap)
    for k in parts:
        others = {q: v for q, v in parts.items() if q != k}
        if others:
            without, _ = book_weights(others, {q: budgets[q] for q in others}, gross_cap)
            assert not np.allclose(book.to_numpy(), without.to_numpy()), f"the book ignores {k}"

An interactive laboratory

The lab assembles the three components on daily data from 3 September 1927 to 31 August 2026, with equal capital in each component that is switched on (a third each when all three are). It recomputes everything in the browser with a TypeScript port of the Python, and the site’s build checks that the two agree.

Figure 4A book built from components
Components in the book
Trend hysteresis band
Vol-target no-trade band
Trading cost
Gross exposure cap

3 September 1927 to 31 August 2026, equal capital in 3 components: the book’s Sharpe ratio was 0.95, its annual volatility 8.0% and its worst drawdown −26.3%. Netting the components’ trades saved 0.4% of their turnover.

Growth of 1 (log scale)1101001k10k100kBook exposure: market, then momentum stacked0.0×0.5×1.0×1.5×Book drawdown−20%−10%0%19401960198020002020
Growth: the bookTrend on the market, aloneVol-targeted market, aloneVol-targeted momentum, alone
Exposure: marketExposure: momentumDrawdown
ComponentSharpe aloneShare of returnShare of riskBook Sharpe without itDrawdown without it, excess, at 10% vol
Trend on the market0.6834%43%1.02−37.6%
Vol-targeted market0.5625%35%1.04−30.7%
Vol-targeted momentum1.0041%22%0.67−33.4%

The book as selected: Sharpe ratio 0.95, drawdown −31.9% on the same basis as the last column. Kenneth R. French Data Library, daily: the market in excess of T-bills and the momentum factor. Shares of return and risk are before costs and sum to 100% across the components; Sharpe ratios are after costs. The last column compounds each book’s return in excess of T-bills after scaling it, with hindsight, to 10% annual volatility, so drawdowns compare at equal risk.

Kenneth R. French Data Library, daily. Top: growth of one unit for the book and for each component alone. Middle: the book’s exposure to the market and, stacked above it, to momentum. Bottom: the book’s drawdown. The table measures each component three ways. Suggested experiments: switch off the vol-targeted market component and watch the book’s Sharpe ratio rise; switch off trend instead and compare the drawdowns at equal risk; set the gross cap to 1× and watch the overlay scale the whole book; remove the bands and raise the cost to 20 bp.

Measuring a contribution

How much does each component contribute? Three answers are in common use, and on this book they disagree.

Alone

Run on its own capital, the trend component has a Sharpe ratio of 0.68, the volatility-targeted market 0.56 and the volatility-targeted momentum 1.00. The book of all three, with equal capital, has 0.95: below its best component. Standalone numbers describe a component held on its own, and say nothing about what it adds to the others.

Inside the book

The book’s excess return is the sum of each component’s share of the book’s positions times the instruments’ returns, so return contributions add up exactly. Risk contributions add up too: a component’s share of the book’s variance is the covariance of its piece with the whole book, divided by the book’s variance, the Euler decomposition that risk managers have used since Litterman’s “hot spots”.8 Measured this way, trend carries 43% of the book’s risk and earns 34% of its return; the volatility-targeted market carries 35% and earns 25%; momentum carries only 22% and earns 41%. The two market components hold the same instrument most of the time, so their risks add: their daily P&L correlates with the rest of the book at 0.67 for trend and 0.59 for the volatility-targeted market. Momentum’s correlates at only 0.20, so it carries less of the book’s risk than it earns.

Leave one out, at equal risk

The most direct question is what the book would look like without the component. Removing a component changes the book’s risk, so the comparison has to be made at equal risk: the Sharpe ratio is unaffected by scaling, and drawdowns are compared after scaling each book’s excess return, with hindsight, to the same volatility. A difference in Sharpe ratios also needs its uncertainty, which we estimate with a block bootstrap that resamples whole years together.

ComponentSharpe aloneShare of returnShare of riskBook Sharpe without it± (bootstrap)Drawdown without it, excess, at 10% vol
Trend0.6834%43%1.020.04−37.6%
Vol-targeted market0.5625%35%1.040.05−30.7%
Vol-targeted momentum1.0041%22%0.670.06−33.4%

Kenneth R. French Data Library, daily, 3 September 1927 to 31 August 2026; equal capital budgets; 5 bp per unit traded after netting. The book of all three has a Sharpe ratio of 0.95 and a drawdown of −31.9% on the same basis. Sharpe ratios in excess of T-bills; shares before costs. “±” is the standard error of the change in the book’s Sharpe ratio, from a paired block bootstrap with blocks of 250 sessions. Drawdowns compound each book’s return in excess of T-bills after scaling it, with hindsight, to 10% annual volatility.

Momentum is the component the book cannot do without: its Sharpe ratio is the highest and, more to the point, leaving it out drops the book’s from 0.95 to 0.67, a gap about five times its standard error, and one that appears in each of the three eras we checked. Without the volatility-targeted market component the Sharpe ratio rises to 1.04, but the gain is less than two standard errors, it reverses in 1927–1963 (by 0.02), and the drawdown at equal risk barely moves (−31.9% to −30.7%). Beside trend, which trades the same market, it looks redundant; it is not shown to be harmful. Leaving trend out also raises the Sharpe ratio, to 1.02, but deepens the drawdown at equal risk to −37.6%. Most of that protection comes from one episode, the long decline that bottomed in July 1942, and in 2000–2026 the book without trend had the smaller drawdown (−13.7% against −19.0%). Whether that insurance is worth a lower Sharpe ratio depends on what the book is for, and no statistic settles it.

Netting, finally, saved little here: 0.4% of the components’ combined turnover. Netting saves only when two components trade the same instrument on the same day in opposite directions, and the two market components rarely trade on the same day at all: on 35 days in the whole history, 7 of them in opposite directions. Trend switches a few times a year, and the volatility target re-trades only when its weight leaves the band. Combining strategies into one book works through a case where the saving is large.

A checklist

  1. One interface: data up to a close in, target weights per instrument out. No component sees capital, orders or another component.
  2. Inside each component, keep signal, target and sizing as separate steps, each testable alone.
  3. Draw every state machine, including warm-up, and write down which state must survive a restart; persist it, and check it against a replay.
  4. Test invariants and causality for every component before judging any backtest, with a causality test that perturbs the future; and test that the book consumes every component.
  5. Charge costs and apply book-wide limits after netting, in the aggregator and the overlay.
  6. Judge a component alone, inside the book and by leaving it out at equal risk, with the uncertainty of each difference; trust the verdict only when the measures agree or the disagreement is explained.

References

  1. Parnas, D. L. (1972). On the criteria to be used in decomposing systems into modules. Communications of the ACM, 15(12), 1053–1058. https://doi.org/10.1145/361598.361623
  2. Grinold, R. C., & Kahn, R. N. (2000). Active portfolio management: A quantitative approach for producing superior returns and controlling risk (2nd ed.). McGraw-Hill, New York.
  3. Carver, R. (2015). Systematic trading: A unique new method for designing trading and investing systems. Harriman House, Petersfield.
  4. QuantConnect (n.d.). Algorithm framework overview. QuantConnect documentation. www.quantconnect.com/docs/v2/writing-algorithms/algorithm-framework/overview
  5. French, K. R. (n.d.). Data Library: Fama/French factors (daily) and momentum factor (daily). Tuck School of Business, Dartmouth College. mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
  6. Barroso, P., & Santa-Clara, P. (2015). Momentum has its moments. Journal of Financial Economics, 116(1), 111–120. https://doi.org/10.1016/j.jfineco.2014.11.010
  7. QuantConnect (n.d.). Warm up periods. QuantConnect documentation. www.quantconnect.com/docs/v2/writing-algorithms/historical-data/warm-up-periods
  8. Litterman, R. (1996). Hot spots and hedges. Journal of Portfolio Management, 23(5), 52–75. https://doi.org/10.3905/jpm.1996.052