QuantJourney Backtester

QuantJourney Backtester

Share product feedback

✓

Thank you.

Your note is now in the QuantJourney inbox.

docs/engine/weights.mdx

Weights

How target portfolio exposure is produced and transformed into realized portfolio state.

Weights are target portfolio exposure. They are not orders, fills or final positions. The engine later shifts, risk-adjusts, rebalances, charges costs and converts realized exposure into positions.

Sourcebacktester/core.py + backtester/portfolio/rebalance.py
LayerPortfolio target exposure
ModeWeight mode only
Inputsignals, scores, constraints and portfolio rules
Outputtarget weights DataFrame indexed by date and instrument
Primary API_compute_weights(self) -> pd.DataFrame
Main caveatTarget weights are shifted and rebalanced before becoming realized exposure.

Engine Contract

The weight hook answers: "What exposure do I want?" It does not answer: "What order filled?", "What shares do I own?" or "What price did I trade?" Those are later accounting and execution questions.

signals -> target_weights -> shift(1) to avoid same-bar look-ahead -> cash buffer -> optional risk model -> RebalanceEngine -> actual realized weights -> positions and NAV

Data Contract

Contract itemRequirement
Shapedates x instruments DataFrame
Indexaligned to engine trading dates
Columnsengine universe instruments
Valuesportfolio weights, for example 0.25, 0.0, -0.10
Row sumcan be less than 1.0 for cash; can exceed 1.0 only if leverage is intentionally modeled
NaN policyfill explicitly before returning
Timingraw weights are shifted by the performance path before returns are earned

Level 1: Equal Weight Active Signals

This is the canonical long/cash allocation pattern.

Equal weight active names
python
class EqualWeightSignals(Backtester):
    def _compute_weights(self) -> pd.DataFrame:
        signals = self.instruments_data.get_feature(
            "strategies", self.strategy_name, "signals"
        )

        active = signals == 1
        n_active = active.sum(axis=1)

        weights = active.div(n_active, axis=0).fillna(0.0)
        return weights

Level 2: Capped Exposure With Cash Sleeve

Use this when you want residual cash instead of forcing 100 percent exposure.

Cap each position and preserve cash
python
class CappedWeights(Backtester):
    def _compute_weights(self) -> pd.DataFrame:
        signals = self.instruments_data.get_feature(
            "strategies", self.strategy_name, "signals"
        )

        active = signals == 1
        n_active = active.sum(axis=1)

        raw = active.div(n_active, axis=0).fillna(0.0)
        capped = raw.clip(upper=float(self.max_position_size))

        # Do not renormalize. Unallocated weight remains cash.
        return capped.fillna(0.0)

Level 3: Rank-Based Top-N Portfolio

Continuous signals can be transformed into top-N weights.

Top 10 ranked names
python
class TopNWeights(Backtester):
    def _compute_weights(self) -> pd.DataFrame:
        score = self.instruments_data.get_feature(
            "strategies", self.strategy_name, "signals"
        )

        top_n = score.rank(axis=1, ascending=False) <= 10
        weights = top_n.astype(float)
        weights = weights.div(weights.sum(axis=1), axis=0)

        return weights.fillna(0.0)

Level 4: Long/Short Dollar-Neutral Weights

Long/short weights should make gross and net exposure explicit.

Top long / bottom short
python
class LongShortWeights(Backtester):
    def _compute_weights(self) -> pd.DataFrame:
        score = self.instruments_data.get_feature(
            "strategies", self.strategy_name, "signals"
        )

        long_mask = score.rank(axis=1, ascending=False) <= 10
        short_mask = score.rank(axis=1, ascending=True) <= 10

        longs = long_mask.astype(float).div(long_mask.sum(axis=1), axis=0) * 0.50
        shorts = short_mask.astype(float).div(short_mask.sum(axis=1), axis=0) * -0.50

        return (longs.fillna(0.0) + shorts.fillna(0.0)).fillna(0.0)

What Happens After Weights Are Returned?

  1. The engine stores the raw target weights.
  2. Weight-mode performance shifts targets by one bar.
  3. The engine applies the cash buffer.
  4. A configured risk model can adjust weights.
  5. RebalanceEngine decides when target weights become actual weights.
  6. Between rebalance dates, actual weights drift with returns.
  7. Positions are derived from actual weights, NAV and prices.
  8. Transaction cost is charged on rebalance-day turnover.

Failure Modes

  • Normalizing capped weights back to 100 percent when the strategy intended cash.
  • Forgetting that raw target weights are not the final realized weights.
  • Returning NaNs from dates before enough signal history exists.
  • Treating weight-mode rebalancing as a market/limit order simulation.
  • Assuming RebalanceAt.OPEN or VWAP_WINDOW implies full open/VWAP fill simulation unless implemented in the performance path.

Audit Checklist

  • Inspect raw target weights and realized portfolio weights separately.
  • Check row sums for cash, leverage and short exposure.
  • Inspect portfolio_data.rebalance_flags.
  • Compare turnover before and after changing rebalance policy.
  • Confirm transaction costs appear only on rebalance days.