# Close docs/api.md implementation gaps + accuracy fixes

## Context

The scout (agentic LLM strategist) uses `docs/api.md` as its API reference. Two classes of
problems were found:

1. **`result_summary` shape is fabricated.** The docs show a flat structure with `total_positions`,
   `win_rate`, `net_pnl`, `sharpe_ratio`, etc. but the actual implementation returns nested barter-rs
   `TradingSummary` output with `instruments`, `assets`, `backtest_metadata` objects. The scout can't
   extract basic stats without navigating a complex nested structure it was never told about.

2. **Sizing methods undocumented.** `fixed_sum`, `percent_of_balance`, `fixed_units` are implemented
   in code but the "Quantity Sizing" section only documents `Fixed` (decimal string) and `Expr`
   (expression tree). The brief `fixed_sum` example in the validate section is unexplained.

## Gap 1: Flatten result_summary with top-level stats

### Problem

The documented flat fields don't exist. The actual `result_summary` JSON is:

```json
{
  "time_engine_start": "...",
  "time_engine_end": "...",
  "instruments": {
    "binance_btcusdt_spot": {
      "pnl": "1240.50",
      "win_rate": { "value": "0.5957" },
      "profit_factor": { "value": "1.83" },
      "sharpe_ratio": { "value": "1.42", "interval": "Daily" },
      ...
    }
  },
  "assets": [ { "exchange": "BinanceSpot", "asset": "usdt", "tear_sheet": { ... } } ],
  "backtest_metadata": { "position_count": 47, ... },
  "condition_audit_summary": { ... }
}
```

### Approach

Add flat top-level summary fields to the `result_summary` JSON, computed from the barter tear
sheet + a DB aggregate query on `paper_run_positions`. Keep the existing nested structure intact
for backwards compatibility.

**Data sources at summary construction time** (`runner.rs` lines 557–587):

| Field | Source |
|-------|--------|
| `total_positions` | `total_position_count` (already in scope) |
| `winning_positions` | aggregate query on `paper_run_positions WHERE pnl > 0` |
| `losing_positions` | aggregate query on `paper_run_positions WHERE pnl <= 0` |
| `win_rate` | `instruments[0].win_rate.value` (single instrument per backtest) |
| `profit_factor` | `instruments[0].profit_factor.value` |
| `sharpe_ratio` | `instruments[0].sharpe_ratio.value` |
| `total_pnl` | `SUM(pnl) + SUM(fees_enter + fees_exit)` from positions (gross) |
| `total_fees` | `SUM(fees_enter + fees_exit)` from positions |
| `net_pnl` | `SUM(pnl)` from positions (barter `pnl_realised` is already net of fees) |

Note: `avg_bars_in_trade` is dropped — it requires the candle interval and isn't directly
available from barter. Can add later if needed.

### Implementation

#### 1. New DAL query: `paper_run_position::aggregate_stats`

**File:** `crates/swym-dal/src/repo/paper_run_position.rs`

Add a function that returns position-level aggregate stats:

```rust
pub struct PositionAggregateStats {
    pub winning: i64,
    pub losing: i64,
    pub total_pnl_net: Decimal,   // SUM(pnl) — already net of fees
    pub total_fees: Decimal,      // SUM(fees_enter + fees_exit)
}

pub async fn aggregate_stats(pool: &PgPool, run_id: Uuid) -> Result<PositionAggregateStats, sqlx::Error>
```

```sql
SELECT
  COUNT(*) FILTER (WHERE pnl > 0) AS winning,
  COUNT(*) FILTER (WHERE pnl <= 0) AS losing,
  COALESCE(SUM(pnl), 0) AS total_pnl_net,
  COALESCE(SUM(fees_enter + fees_exit), 0) AS total_fees
FROM paper_run_positions
WHERE run_id = $1
```

#### 2. Extract tear sheet stats in runner.rs

**File:** `services/paper-executor/src/runner.rs`

After `engine.finish_run(run_state)` and after inserting all positions, but before building the
summary JSON:

```rust
let pos_stats = paper_run_position::aggregate_stats(pool, run_id).await?;

// Single instrument per backtest — extract the first (and only) tear sheet
let tear_sheet = trading_summary.instruments.values().next();
let win_rate = tear_sheet.and_then(|t| t.win_rate.as_ref()).map(|w| w.value);
let profit_factor = tear_sheet.and_then(|t| t.profit_factor.as_ref()).map(|p| p.value);
let sharpe_ratio = tear_sheet.map(|t| t.sharpe_ratio.value);
```

Then include these in the `serde_json::json!({})` block:

```rust
return Ok(serde_json::json!({
    // --- flat summary (scout-friendly) ---
    "total_positions": total_position_count,
    "winning_positions": pos_stats.winning,
    "losing_positions": pos_stats.losing,
    "win_rate": win_rate,
    "profit_factor": profit_factor,
    "total_pnl": pos_stats.total_pnl_net + pos_stats.total_fees,  // gross
    "total_fees": pos_stats.total_fees,
    "net_pnl": pos_stats.total_pnl_net,
    "sharpe_ratio": sharpe_ratio,
    // --- full barter output (retained for detail) ---
    "time_engine_start": trading_summary.time_engine_start,
    "time_engine_end": trading_summary.time_engine_end,
    "instruments": trading_summary.instruments,
    "assets": assets,
    "backtest_metadata": { ... },
    "condition_audit_summary": condition_audit_summary,
}));
```

Apply the same pattern to the tick-backtest path (lines ~794–806) and the live-run path
(lines ~148–153). The live-run path doesn't have `paper_run_positions` — use the tear sheet
fields only (no aggregate query).

## Gap 2: Document sizing methods in Quantity Sizing section

### Problem

The "Quantity Sizing" section (lines 1564–1625) only covers fixed decimals and Expr trees.
The `SizingMethod` shorthand (`{ "method": "fixed_sum", ... }`) is fully implemented but
undocumented — the scout doesn't know it exists.

### Implementation

**File:** `docs/api.md`

Add a "Sizing methods" subsection between "Fixed quantity" and "Dynamic quantity" in the
Quantity Sizing section (~line 1576):

```markdown
#### Sizing methods

Declarative position sizing — the executor computes the base-asset quantity from the method
parameters and the current price/balance at execution time.

##### `fixed_sum`

Buy a fixed amount of quote currency worth of the base asset.

```json
"quantity": { "method": "fixed_sum", "amount": "500" }
```

qty = amount / current_price. If you set amount to "500" and BTC is at $50,000,
the order will be for 0.01 BTC.

##### `percent_of_balance`

Buy a percentage of a named asset's free balance.

```json
"quantity": { "method": "percent_of_balance", "percent": "5", "asset": "usdt" }
```

qty = (balance × percent / 100) / current_price.

##### `fixed_units`

Buy an exact number of base asset units (semantic alias for a fixed decimal, but
explicit in intent).

```json
"quantity": { "method": "fixed_units", "units": "0.01" }
```
```

## Gap 3: Fix result_summary documentation

### Problem

The example JSON in the `result_summary` section (lines 800–839) doesn't match reality.

### Implementation

**File:** `docs/api.md`

Replace the `result_summary` example (lines ~800–840) with the actual shape that will exist
after Gap 1 is implemented. Show the flat summary fields first (the scout cares about these),
then note the full barter output is also present for detailed analysis.

Show both the flat fields and the nested structure, with a note that instruments/assets contain
full barter tear sheet data for clients that need drawdown metrics, sortino/calmar ratios, etc.

## Files to modify

1. **`crates/swym-dal/src/repo/paper_run_position.rs`** — add `aggregate_stats()` query
2. **`services/paper-executor/src/runner.rs`** — extract flat stats, add to result_summary JSON
   (candle backtest path ~line 574, tick backtest path ~line 794, live run path ~line 148)
3. **`docs/api.md`** — fix `result_summary` example, add sizing methods to Quantity Sizing section

## Verification

```bash
cargo check                    # compile-time SQL + type checking
cargo clippy                   # lint

# Run a backtest and inspect the result_summary:
curl -s https://<host>/api/v1/paper-runs/<completed-run-id> | \
  jq '.result_summary | {total_positions, winning_positions, losing_positions, win_rate, profit_factor, total_pnl, total_fees, net_pnl, sharpe_ratio}'

# Verify flat fields are present and correct:
# - total_positions = winning_positions + losing_positions
# - net_pnl = total_pnl - total_fees
# - win_rate matches winning_positions / total_positions

# Verify nested barter output is still present:
curl -s https://<host>/api/v1/paper-runs/<completed-run-id> | \
  jq '.result_summary | {instruments, assets, backtest_metadata}'
```
