Portfolio Agent EvalsHarbor task suite · ETF analyze → backtest → rebalance
Backtest Engine Correctness/pf-bt-calendar-rebalance

Fixed-weight monthly rebalance with next-open execution, costs, whole shares and dividends

Implement a precisely specified fixed-weight monthly-rebalance backtest with next-open execution, per-fill costs, whole shares and cash-settled dividends; match the oracle equity curve to 1e-6.

T2mediumBacktestmulti-metricready
Edit
readystatic
Agent budget
40 min
Verifier budget
5 min
Tier target
60–80% pass expected

instruction.md

What the agent sees (CONVENTIONS.md is appended automatically)

Fixed-weight monthly rebalance backtest

Simulate the IPS target weights from 2012-01-03 (initial cash 1,000,000 USD) to 2024-12-31:

  • Rebalance decision on the last trading day of each month using that day's closes; orders execute at the next trading day's open.
  • Target shares = floor(target_weight x portfolio_value_at_decision / decision_close). Trade the difference. Sells execute before buys. Cash may never go negative: if it would, scale buys down (largest notional first, reducing one share at a time) until feasible.
  • Costs: 5 bps of traded notional plus 1.00 USD per non-zero fill, charged to cash at execution.
  • Dividends: cash credited on pay_date based on shares held at the ex-date close.
  • Cash earns 0.

Write /app/output/equity.csv (date, cash, market_value, total, turnover) for every trading day, /app/output/trades.csv (date, ticker, side, shares, price, cost) and /app/output/stats.json (cagr, ann_vol, sharpe, sortino, max_drawdown, calmar, n_trades, total_costs, avg_annual_turnover) per CONVENTIONS.md.

Verification

reward.json metrics · weights sum to 1.00 · tolerance rel 1e-6

MetricWeightCheck
equity_curve
0.5
total within tolerance on every trading day; linear credit by fraction of matching days.
trades_exact
0.25
Same (date, ticker, shares) multiset as oracle.
stats
0.25
All stats within tolerance.

Harbor scaffold

Generated from this record — task.toml, Dockerfile, verifier, oracle stub

schema_version = "1.4"

[task]
name = "portfolio-agent-evals/pf-bt-calendar-rebalance"
version = "1.0.0"
description = "Implement a precisely specified fixed-weight monthly-rebalance backtest with next-open execution, per-fill costs, whole shares and cash-settled dividends; match the oracle equity curve to 1e-6."
keywords = ["etf", "portfolio", "backtest", "backtest-engine", "backtest-mechanics", "execution-timing", "cost-modelling", "dividends"]

[metadata]
author_name = "portfolio-agent-evals"
difficulty = "medium"
category = "quant-finance"
tags = ["backtest-engine", "tier-2", "backtest", "multi-metric"]
theme = "Backtest Engine Correctness"
tier = 2
reward_type = "multi-metric"

[agent]
timeout_sec = 2400.0

[verifier]
timeout_sec = 300.0

[environment]
# Offline by design: all data is synthetic and generated at build time.
network_mode = "none"
cpus = 2
memory_mb = 4096
storage_mb = 10240
build_timeout_sec = 900.0

Traps

Each must carry signal: a trap-blind solution must lose credit

  • Last trading day of month, not last calendar day; December decision executes in January.
  • A dividend whose pay-date falls after the position was sold is still paid (ex-date shares).
  • Zero-share fills must not incur the fixed fee.
  • The cash-scaling rule is deterministic (largest notional first, one share at a time).

Inputs

Fixtures mounted in the environment

Outputs

What the verifier reads from /app/output

  • /app/output/equity.csv
    CSV
    Daily equity curve.
  • /app/output/trades.csv
    CSV
    Every fill.
  • /app/output/stats.json
    JSON
    Summary statistics.

Anti-gaming

Prices and dividend schedules are seed-generated; the oracle engine is not in the image.

Oracle notes

solution/solve.sh must score 1.0 on five seeds

Reference engine tests/ref/engine.py (~200 lines) shared by all engine tasks.

Reviews (0)

Design review before a task is marked ready

    Reviews are read-only in static export.
    Tier 2 · Practitioner60–80% pass expected. Created 2026-01-01, updated 2026-01-01.