Skip to content

Midas

Target-weight allocation engine for your portfolio. Strategies emit continuous conviction scores, an allocator blends them into target portfolio weights via sigmoid transform, and a rebalancer diffs against current holdings to generate trades. Backtest against years of historical data with train/test splits, or run it live with real-time polling. You pull the trigger.

Requires uv and Python 3.14+.

Quick Start

uv sync
uv run midas strategies
uv run midas backtest -p portfolio.yaml -s strategies.yaml --start 2023-01-01 --end 2024-12-31 -o results.csv
uv run midas live -p portfolio.yaml -s strategies.yaml
uv run midas optimize -p portfolio.yaml --start 2023-01-01 --end 2024-12-31

Config

Portfolio

# portfolio.yaml
portfolio:
  - ticker: VOO
    shares: 5
    cost_basis: 420.00
  - ticker: AAPL
    shares: 10
    cost_basis: 155.00

available_cash: 2000.00

trading_restrictions:
  round_trip_days: 30  # can't buy then sell (or vice versa) the same ticker within 30 days

cash_infusion:
  amount: 1500.00
  next_date: 2026-04-03
  frequency: biweekly

Strategies

# strategies.yaml
sigmoid_steepness: 2.0        # how aggressively the allocator responds to conviction
rebalance_threshold: 0.02     # ignore weight diffs smaller than 2%
min_cash_pct: 0.05            # always keep 5% cash
# max_position_pct: 0.20      # omit to auto-compute from portfolio size

strategies:
  - name: MeanReversion
    weight: 1.5              # blending influence (conviction strategies only)
    params:
      window: 30
      threshold: 0.10
  - name: StopLoss
    veto_threshold: -0.5     # score at or below which position is force-liquidated
    params:
      loss_threshold: 0.10
  - name: DollarCostAveraging
    params:
      frequency_days: 14
      amount: 500.00        # dollar amount per DCA buy

Strategy Tiers

Each strategy has an intrinsic tier determined by its class:

Tier Behavior Strategies
CONVICTION Scores blended via weighted average into target weights MeanReversion, Momentum, RSIOversold, RSIOverbought, BollingerBand, MACDCrossover, VWAPReversion, MovingAverageCrossover, GapDownRecovery, ProfitTaking
PROTECTIVE Evaluated after blending; vetoes position (forces weight to 0) if score <= veto_threshold StopLoss, TrailingStop
MECHANICAL Bypasses allocator entirely; generates independent order intents DollarCostAveraging

Strategy Knobs

Field Scope Default Description
sigmoid_steepness Global 2.0 Controls how aggressively the allocator responds to conviction scores. Higher = more extreme position sizes
rebalance_threshold Global 0.02 Minimum weight diff to trigger a rebalance trade. Higher = fewer, larger trades
min_cash_pct Global 0.05 Minimum cash allocation as a fraction of portfolio value. Higher = more conservative
max_position_pct Global auto Maximum weight for any single position. Omit to auto-compute from portfolio size
weight CONVICTION only 1.0 How much influence this strategy has in the blended score
veto_threshold PROTECTIVE only -0.5 Score at or below which the strategy forces target weight to 0.0
params All {} Strategy-specific parameters (window sizes, thresholds, etc.)

All knobs except min_cash_pct are tunable by the optimizer.

Optimizer

The optimizer uses Bayesian optimisation (Optuna TPE) to search over all tunable parameters jointly:

Layer What it controls Examples
Strategy parameters When a strategy fires and how strong window, threshold, loss_threshold
Strategy weights How much influence each conviction strategy has in the blend _weight: 0.5 to 3.0
Veto thresholds When a protective strategy overrides the blend _veto_threshold: -0.8 to -0.2
Sigmoid steepness How aggressively the allocator responds to conviction sigmoid_steepness: 1.0 to 5.0
Rebalance threshold Minimum weight diff to trigger a trade rebalance_threshold: 0.01 to 0.05
Max position % Maximum weight for any single position max_position_pct: 0.15 to 0.50
uv run midas optimize -p portfolio.yaml --start 2015-01-01 --end 2024-12-31 -o optimized.yaml

Development

uv sync                     # install all deps
uv run pytest               # run tests
uv run ruff check . --fix   # lint
uv run ruff format .        # format
uv run mypy src             # type check