Filters

Filters are the Trade Director’s mechanism for portfolio-level risk management. They evaluate trade requests and can approve, reject, or modify orders before execution.

Filter types

Individual filters evaluate one order at a time. Examples include weekday blocking and VAR limits.

Batch filters evaluate all pending orders for an epoch simultaneously. This allows ranking and selection, such as approving only the top N signals. Batch filters run after individual filters.

Specifying filters

In configuration files

{
  "filters": [
    { "name": "weekday", "blocked-days": ["Friday"] },
    { "name": "max-positions", "max-long-positions": 5, "max-short-positions": 5 }
  ]
}

On the command line

algo run --strategy kbt --symbols @ES,@NQ --filter max-positions \
    --filter-inputs 'max-long-positions=3,max-short-positions=3'

The CLI --filter flag replaces any filters defined in the config file. Array values use semicolons: blocked-days=Friday;Monday.

Per-strategy filters

Runs can define their own filter chains that execute after global filters:

{
  "filters": [
    { "name": "max-positions", "max-long-positions": 10 }
  ],
  "runs": [
    {
      "strategy": "goldencross",
      "filters": [
        { "name": "weekday", "blocked-days": ["Monday", "Friday"] }
      ],
      "symbol": "@ES"
    }
  ]
}

Built-in filters reference

nil

Passthrough filter that approves all trades. Useful for baseline comparisons.

weekday

Blocks entries on specified days of the week.

ParameterTypeDefaultDescription
blocked-daysarray["Friday"]Days to block (full or abbreviated names)
block-fridaybooltrueConvenience toggle for Friday

Day names can be full (“Friday”) or abbreviated (“Fri”, “fri”).

Note: For daily bars, the filter checks the fill day (next trading day after signal), not the signal day.

max-positions

Limits the number of concurrent open positions across the portfolio, with separate limits for long and short positions.

ParameterDefaultDescription
max-long-positions1Maximum long positions (-1 = unlimited)
max-short-positions1Maximum short positions (-1 = unlimited)

Only blocks entry orders; exits always pass through. The filter counts positions by direction across all strategies in the portfolio.

Example use cases:

// Long-only portfolio: allow longs but block all shorts
{"name": "max-positions", "max-long-positions": 5, "max-short-positions": 0}

// Balanced exposure: allow equal long and short positions
{"name": "max-positions", "max-long-positions": 3, "max-short-positions": 3}

// Unlimited shorts, limited longs
{"name": "max-positions", "max-long-positions": 2, "max-short-positions": -1}

var

Value at Risk filter. Rejects trades that would exceed portfolio VAR limits.

ParameterDefaultDescription
max-var100000Maximum total portfolio VAR
max-var-per-trade10000Maximum VAR per single trade
default-stop-pct0.02Default stop loss percentage for VAR calculation

risk-budget

Sets position size so each trade’s notional exposure is a fixed fraction of portfolio equity. The filter overrides the quantity set by the strategy’s Buy/Sell verbs.

ParameterDefaultDescription
max-trade-risk-pct0.10Maximum percentage of equity as notional exposure per trade

How it works:

  1. Calculates portfolio equity: initial capital + closed profit + open profit
  2. Calculates allowed notional: equity x max-trade-risk-pct
  3. Calculates notional per contract: price x point value
  4. Sets the trade quantity to floor(allowed notional / notional per contract)
  5. Rejects the trade if the budget cannot afford even 1 contract

Key behavior: If the strategy requests 1 contract but the budget allows 4, the quantity is increased to 4. If the strategy requests 10 but the budget allows 4, it is reduced to 4. The risk budget – not the strategy – is the authority on position sizing.

Example: With $1M capital and max-trade-risk-pct: 0.10 (10%): allowed notional = $100,000. Corn (@ZC at 450, point value 50): notional per contract = 450 x 50 = $22,500. Filter sets quantity to 4 contracts (100000 / 22500 = 4).

Note: Requires sequential mode (reads portfolio-level state).

atr-position-size

Sizes positions using the Turtle Trading N-units approach. Computes ATR (Average True Range) from recent bar data and sizes each trade so that the dollar volatility per position equals a fixed percentage of portfolio equity. Volatile instruments get fewer contracts; calm ones get more.

ParameterDefaultDescription
risk-pct0.01Percentage of equity to risk per trade (1%)
atr-bars14Number of bars for ATR lookback

How it works:

  1. Computes ATR using Turtle-style exponential smoothing: N = ((period-1)*N + TR) / period
  2. Calculates portfolio equity: initial capital + closed profit + open profit
  3. Calculates dollar volatility per contract: ATR x point value
  4. Sets the trade quantity to floor(equity x risk-pct / dollar volatility), minimum 1

Example: With $1M capital and risk-pct: 0.01 (1%): risk budget = $10,000.

  • E-mini S&P (@ES, ATR=50, PV=50): dollar vol = 50 x 50 = $2,500 -> 4 contracts
  • Heating Oil (@HO, ATR=0.08, PV=42000): dollar vol = 0.08 x 42000 = $3,360 -> 2 contracts
  • Corn (@ZC, ATR=10, PV=50): dollar vol = 10 x 50 = $500 -> 20 contracts

Note: Requires sequential mode (reads portfolio-level state and strategy bar data).

position-size

Adjusts position sizes based on various modes.

ParameterDefaultDescription
mode"fixed""fixed", "risk-parity", or "equal-weight"
risk-per-trade0.01Risk per trade as fraction of capital
max-position-pct0.1Maximum position as fraction of capital

asset-class

Limits exposure by asset class.

ParameterDescription
asset-class-limitsMaximum exposure value per asset class
asset-class-max-positionsMaximum position count per asset class

category

Limits exposure by futures category (energy, metal, index, etc.).

ParameterDescription
category-limitsMaximum exposure value per category
category-max-positionsMaximum position count per category

volatility

Rejects trades when recent realised volatility exceeds a threshold.

ParameterDefaultDescription
lookback-bars20Volatility calculation period
max-threshold0.03Maximum volatility threshold

top-n

Batch filter that approves only the top N entry orders by signal strength (TDValue). Exit orders are always approved unless position continuity suppresses them.

ParameterDefaultDescription
max-orders4Maximum entry orders to approve per epoch
preserve-positionstrueSuppress unnecessary exit-and-re-entry of existing positions

Behavior:

  • Sorts entry orders by TDValue descending (higher = stronger signal)
  • Approves top N entries, rejects the rest
  • Orders without TDValue are treated as TDValue=0
  • Ties are broken by strategy ID alphabetically for determinism

Position continuity: When preserve-positions is enabled (default), the filter prevents unnecessary exit-and-re-entry of existing positions. If a strategy has an existing position and wants to continue it (entry in same direction), and the continuation entry ranks in the top-n, the paired exit order is suppressed. This reduces slippage and commission costs.

top-n-long-short

Like top-n, but ranks long and short orders separately.

ParameterDefaultDescription
max-longs4Maximum long (Buy) orders to approve per epoch
max-shorts4Maximum short (Sell) orders to approve per epoch
max-ordersLegacy: sets both max-longs and max-shorts
preserve-positionstrueSuppress unnecessary round-trips

Long and short orders are ranked by TDValue independently. Long continuations compete in the longs pool only; short continuations compete in the shorts pool only. Position continuity works the same as top-n.

The audit trail

Generate a JSON audit trail of every filter decision:

algo run --config portfolio.json --audit-trail audit.json

Each entry records the timestamp, strategy, symbol, order type, price, whether it was approved, which filter processed it, and the rejection reason (if any).