# Strategy Schema Extension + Seed Infrastructure

## Context

We're beginning a new phase: scoring strategies based on backtest results. Before scoring, we need named, versioned, described strategies in the database. Currently strategies are anonymous blobs identified only by a content hash. We need:

1. Schema extensions: `name`, `version`, `description` on the `strategies` table
2. Seed infrastructure: environment-aware strategy seeding that runs on startup
3. Seed definitions for 11 strategies (some fully implemented, some placeholders)

## DSL capability assessment

The rule-based DSL supports: OHLCV fields with bar offsets, `highest/lowest/sma/ema/rsi/std_dev` rolling functions over a single candle field, arithmetic (`bin_op`), comparisons, `cross_over/cross_under`, boolean combinators (`all_of/any_of/not`), and position state checks.

**Hard limits**: `Func` takes a single `CandleField` — cannot nest functions (no EMA-of-RSI, no MACD signal line). No ATR, ADX, VWAP, WMA, KAMA, or Stochastic %D. No forward-projection. No stateful cross-bar variables.

| Strategy | Status | What works / what's missing |
|---|---|---|
| **HODL** | Implementable | `flat → buy` |
| **Simple trend + regime filter** | Partial | EMA crossover works; ADX/choppiness not available |
| **Buy 2 factors, reversion entry** | Partial | RSI + SMA(200) filter works; ADX not available |
| **Ichimoku** | Partial | Tenkan/Kijun crossover via arithmetic on highest/lowest; cloud not possible |
| **Bull market support band** | Partial | SMA(100)/EMA(105) bounce detection works |
| **Momentum cascade** | Placeholder | Needs ADX, ATR, choppiness index, EMA ribbon |
| **Gaussian channel** | Placeholder | Needs nested EMA (multi-pole filter) |
| **Supertrend** | Placeholder | Needs ATR + stateful band flip |
| **Money line** | Placeholder | Needs VWAP |
| **Trend detector** | Placeholder | Needs KAMA or Hull MA |
| **Stochastic + Keltner** | Placeholder | Needs ATR for Keltner channels |
| **Hull suite** | Placeholder | Needs WMA, nested functions |

## Step 1 — Migration

**New file**: `crates/swym-dal/migrations/20260302000001_strategy_metadata.sql`

```sql
ALTER TABLE strategies
    ADD COLUMN name        TEXT,
    ADD COLUMN version     INTEGER NOT NULL DEFAULT 1,
    ADD COLUMN description TEXT;

CREATE UNIQUE INDEX idx_strategies_name_version
    ON strategies (name, version) WHERE name IS NOT NULL;
```

- Columns nullable for backward compat with existing hash-only strategies
- Partial unique index on `(name, version)` enforced only when `name IS NOT NULL`

## Step 2 — Model + repo updates

**`crates/swym-dal/src/models/strategy.rs`** — add to `StrategyRow`:
- `name: Option<String>`
- `version: i32`
- `description: Option<String>`

**`crates/swym-dal/src/repo/strategy.rs`**:
- Update `COLS` constant to include new columns
- Update existing `upsert()` to pass name/version/description (optional params)
- Add `upsert_named()` — upserts by `(name, version)` instead of by hash:
  ```sql
  INSERT INTO strategies (name, version, description, hash, config, hash_input)
  VALUES ($1, $2, $3, $4, $5, $6)
  ON CONFLICT (name, version) WHERE name IS NOT NULL
  DO UPDATE SET description = EXCLUDED.description,
               hash = EXCLUDED.hash,
               config = EXCLUDED.config,
               hash_input = EXCLUDED.hash_input
  RETURNING ...
  ```

## Step 3 — Seed infrastructure

Rust-based seed runner in swym-dal. Strategy configs defined as `serde_json::json!()` in Rust source files — this reuses `normalize_strategy()` + `compute_strategy_hash()` so hash computation isn't duplicated in SQL.

**New files**:
- `crates/swym-dal/src/seeds/mod.rs` — `StrategySeed` struct + `run_seeds(pool, env)` entry point
- `crates/swym-dal/src/seeds/common.rs` — 11 strategy definitions (all environments)
- `crates/swym-dal/src/seeds/dev.rs` — stub for future dev-only seeds

**Seed runner** (`mod.rs`):
```rust
pub struct StrategySeed {
    pub name: String,
    pub version: i32,
    pub description: String,
    pub config: serde_json::Value,
}

pub async fn run_seeds(pool: &PgPool, env: &str) -> Result<(), DalError> {
    let common = common::strategies();
    for seed in &common {
        let hash_input = normalize_strategy(&seed.config);
        let hash = compute_strategy_hash(&hash_input);
        strategy::upsert_named(pool, &seed.name, seed.version, &seed.description,
                               &hash, &seed.config, &hash_input).await?;
    }
    if env == "dev" { /* run dev::strategies() */ }
    Ok(())
}
```

**`crates/swym-dal/src/lib.rs`** — add `pub mod seeds;`

**`crates/swym-dal/src/config.rs`** — add `seed_env: Option<String>` to `DatabaseConfig` (serde default `None`)

**`services/api/src/main.rs`** — after `connect()`:
```rust
if let Some(env) = &config.database.seed_env {
    swym_dal::seeds::run_seeds(&pool, env).await?;
}
```

**`config/dev/api.json`** — add `"seed_env": "dev"` to the `database` block

## Step 4 — Seed strategy definitions

All in `seeds/common.rs`. Each gets `version: 1`. Descriptions are markdown. Quantities use `"0.001"`.

### Implementable (real rules)

**HODL** — `"1d"`, 1 rule:
- `position: flat` → `buy`

**Simple trend + regime filter** — `"1h"`, 2 rules:
- `flat` + `cross_over(ema(close,20), ema(close,50))` → `buy`
- `long` + `cross_under(ema(close,20), ema(close,50))` → `sell`

**Buy 2 factors, reversion entry** — `"1h"`, 2 rules:
- `flat` + `rsi(close,14) < 30` + `close > sma(close,200)` → `buy`
- `long` + `rsi(close,14) > 70` → `sell`

**Ichimoku (core)** — `"1h"`, 2 rules:
- Tenkan expr = `(highest(high,9) + lowest(low,9)) / 2`
- Kijun expr = `(highest(high,26) + lowest(low,26)) / 2`
- `flat` + `cross_over(tenkan, kijun)` + `close > kijun` → `buy`
- `long` + `cross_under(tenkan, kijun)` → `sell`

**Bull market support band** — `"1d"`, 2 rules:
- `flat` + `close > sma(close,100)` + `close > ema(close,105)` + `close[1] < sma(close,100,offset=1)` → `buy`
- `long` + `close < sma(close,100)` + `close < ema(close,105)` → `sell`

### Placeholders (empty rules)

**Momentum cascade, Gaussian channel, Supertrend, Money line, Trend detector, Stochastic + Keltner, Hull suite** — each gets `{ "type": "rule_based", "candle_interval": "1h", "rules": [] }` with a detailed markdown description including:
- Full concept and component breakdowns
- Entry/exit logic
- `> **Status: placeholder** — requires DSL extensions: [specific missing features]`

## Step 5 — API handler updates

**`services/api/src/handlers/strategies.rs`**:
- Add `name: Option<String>`, `version: i32`, `description: Option<String>` to `StrategyResponse` (from `StrategyRow`)
- Add optional `name`, `description` to `CreateStrategyRequest`
- In `create_strategy`: pass name/description through to the repo `upsert` call

## Step 6 — Dashboard updates

**`dashboard/src/types/api.ts`** — add `name: string | null`, `version: number`, `description: string | null` to `Strategy`

**`dashboard/src/pages/StrategiesPage.tsx`** — add Name column; show `strategy.name ?? shortHash(strategy.hash)`

**`dashboard/src/pages/StrategyDetailPage.tsx`** — show name + version badge in header; render description markdown below

**`dashboard/src/pages/AddStrategyPage.tsx`** — add optional name and description form fields

## Files to create/modify

| File | Action |
|---|---|
| `crates/swym-dal/migrations/20260302000001_strategy_metadata.sql` | **Create** |
| `crates/swym-dal/src/models/strategy.rs` | Modify — add name/version/description |
| `crates/swym-dal/src/repo/strategy.rs` | Modify — update COLS, add `upsert_named` |
| `crates/swym-dal/src/seeds/mod.rs` | **Create** — seed runner |
| `crates/swym-dal/src/seeds/common.rs` | **Create** — 11 strategy definitions |
| `crates/swym-dal/src/seeds/dev.rs` | **Create** — stub |
| `crates/swym-dal/src/lib.rs` | Modify — add `pub mod seeds` |
| `crates/swym-dal/src/config.rs` | Modify — add `seed_env` |
| `services/api/src/main.rs` | Modify — call `run_seeds` |
| `services/api/src/handlers/strategies.rs` | Modify — name/version/description |
| `config/dev/api.json` | Modify — add `seed_env` |
| `dashboard/src/types/api.ts` | Modify |
| `dashboard/src/pages/StrategiesPage.tsx` | Modify — name column |
| `dashboard/src/pages/StrategyDetailPage.tsx` | Modify — name/version/description display |
| `dashboard/src/pages/AddStrategyPage.tsx` | Modify — name/description fields |

## Verification

1. `cargo check` — compile-time SQL verification against running DB
2. `cargo clippy` — lint
3. Deploy to dev: `./script/deploy.sh dev drop` for clean schema
4. `GET /api/v1/strategies` — should return 11 named strategies
5. Verify: 5 with real rules (rule counts > 0), 6 placeholders (0 rules)
6. `POST /api/v1/strategies` with name/description — verify they persist
7. Dashboard: list shows names, detail shows description
8. `cd dashboard && pnpm tsc --noEmit` — frontend type-check
