# Strategy API

#[strategy], the Strategy trait, SignalOutput shape, SL/TP emission modes, #[param], #[tag] declarations.

<a id="strategy"></a>
## #[strategy]

```rust
#[strategy](name, description?, data_mode, timeframe?, emit_sltp?, execution?, uses_vp?, columns?)
```

Metadata attribute. Marks a struct as a Rust strategy (TBBO ticks or OHLC bars).

Stripped before compilation; the parser at electron-side reads these kwargs to register the strategy with the UI and pick the right runner (tick vs bar). The struct must `#[derive(Default)]` and implement either the `Strategy` trait (tick) or the `OhlcStrategy` trait (bar).

`data_mode` is REQUIRED. There is no default. "tick" feeds raw TBBO and uses the tick runner; "ohlc" feeds an OHLC bar buffer (native parquet OR TBBO aggregated to `timeframe`) and uses the bar runner. The choice is enforced at metadata parse time: omitting it throws a clear error.

`timeframe` is required when `data_mode = "ohlc"` (e.g. `"1m"`, `"5m"`). Ignored for `data_mode = "tick"`.

`uses_vp = true` enables the per-bar Volume Profile snapshot pre-compute and requires `data_mode = "tick"` with `execution = "bar"`. Use it together with the VpStrategy trait for VP-zone style strategies.

`columns = [...]` is a UI hint about extra TBBO columns the strategy reads via `data.col("name")`. Undeclared columns still work; declaring them just surfaces the list in the inspector.

### Parameters

- `name` (`str`, default `"Unnamed Strategy"`): Display name in the strategy panel
- `description` (`str`, default `""`): Tooltip description
- `data_mode` (`str`, default `required`): "tick" (raw TBBO) or "ohlc" (bar input)
- `timeframe` (`str`, default `none`): Bar timeframe ("1m", "5m", ...). Required when data_mode = "ohlc".
- `emit_sltp` (`str`, default `"per_tick"`): "per_tick" (default; SL/TP re-evaluated each tick) or "entry_only" (locked at entry)
- `execution` (`str`, default `auto`): "tick" or "bar". Auto-set to "bar" when data_mode = "ohlc". For data_mode = "tick" you can opt into bar pacing.
- `uses_vp` (`bool`, default `false`): Enables per-bar VP snapshot pre-compute. Requires data_mode = "tick" + execution = "bar".
- `columns` (`[&str]`, default `[]`): Extra TBBO columns this strategy reads (UI hint)

### Example

```rust
// Tick (TBBO) strategy
#[strategy(
    name = "Microprice Reversion",
    description = "Mean-revert against microprice deviation",
    data_mode = "tick",
    emit_sltp = "per_tick",
    columns = ["volume", "delta"],
)]
#[derive(Default)]
pub struct MicropriceReversion { /* fields */ }

// OHLC bar strategy
#[strategy(
    name = "EMA Cross",
    description = "Fast/slow EMA crossover at bar close",
    data_mode = "ohlc",
    timeframe = "1m",
    emit_sltp = "entry_only",
)]
#[derive(Default)]
pub struct EmaCross { /* fields */ }
```

### Notes

- data_mode = "ohlc" implies execution = "bar". You cannot have OHLC input with tick pacing.
- uses_vp = true cannot be combined with data_mode = "ohlc"; VP snapshots are built from raw ticks.
- OHLC strategies implement OhlcStrategy. Tick strategies implement Strategy. The same struct cannot do both.

<a id="param"></a>
## #[param]

```rust
#[param](default, min?, max?, step?, label?, tooltip?)
```

Field attribute. Marks a struct field as a swept parameter.

Only fields annotated with `#[param]` are filled from the UI. Other fields use `Default::default()` and act as per-combo scratch memory.

### Parameters

- `default` (`literal`, default `required`): Default value (int, float, or string)
- `min` (`literal`, default `auto`): Minimum allowed value
- `max` (`literal`, default `auto`): Maximum allowed value
- `step` (`literal`, default `1 or 0.1`): Slider step
- `label` (`str`, default `field name`): Display label in the params panel
- `tooltip` (`str`, default `""`): Hover tooltip

### Example

```rust
#[param(default = 14, min = 2, max = 200, label = "Period")]
pub period: usize,

#[param(default = 2.0, min = 0.5, max = 5.0, step = 0.25, label = "ATR Mult",
        tooltip = "Stop distance as multiple of ATR")]
pub atr_mult: f64,
```

<a id="tag"></a>
## #[tag]

```rust
#[tag](name, label?, color?, description?)
```

Struct attribute. Declares UI metadata for a tag the strategy emits.

Tags are free-form. You can attach any tag name in `calculate()` via `.with_tag(name, values)` and it will work. `#[tag]` declarations exist purely so the UI shows nice labels and colors instead of auto-generated ones.

### Parameters

- `name` (`str`, default `required`): Tag identifier (matches `with_tag()` key)
- `label` (`str`, default `name`): Display label
- `color` (`str`, default `auto`): Hex color for the tag badge
- `description` (`str`, default `""`): Tooltip description

### Example

```rust
#[tag(name = "morning", label = "Morning Session", color = "#7aa2f7", description = "9:30am-11am ET")]
#[tag(name = "trend", label = "Trend Filter", color = "#26A69A")]
```

<a id="strategy-trait-tick-path"></a>
## Strategy trait (tick path)

```rust
Strategy trait (tick path)impl Strategy for MyStrategy
```

Two methods: prepare() (per-day hoisted work) and calculate() (per-combo tick signal generation). Implemented when data_mode = "tick".

Implement `Strategy` on your struct when data_mode = "tick". `prepare()` is optional and defaults to `DayPrep::empty()`. `calculate()` is required and returns a `SignalOutput` whose arrays have length equal to the tick count.

For data_mode = "ohlc" use the `OhlcStrategy` trait instead. The metadata parser uses your data_mode kwarg to pick the right runner; you only implement the matching trait.

### Example

```rust
impl Strategy for MyStrategy {
    fn prepare(data: &TickData) -> DayPrep {
        let mut prep = DayPrep::empty();
        prep.insert_f64("atr", ta::atr_bid_ask(&data.bid, &data.ask, 30));
        prep
    }

    fn calculate(&self, data: &TickData, prep: &DayPrep) -> SignalOutput {
        let n = data.len();
        let atr = prep.f64("atr").unwrap_or(&[]);
        let mut entry_long = vec![false; n];
        let mut sl_long = vec![f64::NAN; n];

        for i in self.period..n {
            if data.mid[i] < data.bid[i] {
                entry_long[i] = true;
                if let Some(a) = atr.get(i) {
                    sl_long[i] = data.mid[i] - a * 2.0;
                }
            }
        }

        SignalOutput::new(entry_long, vec![false; n], vec![false; n], vec![false; n])
            .with_sl_long(sl_long)
            .with_entry_only_sltp()
    }
}
```

<a id="ohlcstrategy-trait-bar-path"></a>
## OhlcStrategy trait (bar path)

```rust
OhlcStrategy trait (bar path)impl OhlcStrategy for MyStrategy
```

Bar-input sibling of Strategy. Use when data_mode = "ohlc". Receives BarData; returns BarSignalOutput.

Implement `OhlcStrategy` when data_mode = "ohlc". The runner projects bars-as-ticks at write time, so each bar becomes one row in the bundle. `prepare()` is optional (default empty); `calculate()` is required.

Key shape differences vs `Strategy`:
- Receives `&BarData` (open / high / low / close / volume series) instead of `&TickData`.
- Returns `BarSignalOutput` (constructed via `BarSignalOutput::for_bars(n)`) instead of `SignalOutput::new(...)`.
- All output arrays have length `data.len()` (bar count).

### Example

```rust
use qc_strategy_api::prelude::*;

#[derive(Default)]
pub struct EmaCross { pub fast: usize, pub slow: usize }

impl OhlcStrategy for EmaCross {
    fn calculate(&self, data: &BarData, _prep: &DayPrep) -> BarSignalOutput {
        let fast = ta::ema(&data.close, self.fast);
        let slow = ta::ema(&data.close, self.slow);
        let mut out = BarSignalOutput::for_bars(data.len());
        for i in 1..data.len() {
            if fast[i-1] <= slow[i-1] && fast[i] > slow[i] {
                out.entry_long[i] = true;
            }
            if fast[i-1] >= slow[i-1] && fast[i] < slow[i] {
                out.exit_long[i] = true;
            }
        }
        out
    }
}
```

<a id="signaloutput"></a>
## SignalOutput

```rust
SignalOutputSignalOutput::new(entry_long, exit_long, entry_short, exit_short)
```

Return type from calculate(). Builder pattern for SL/TP, tags, and emission mode.

Each of the four bool Vecs has length equal to the tick count. Optional SL/TP arrays are added via `.with_sl_long()`, `.with_tp_long()`, `.with_sl_short()`, `.with_tp_short()`. Emission mode defaults to per-tick; call `.with_entry_only_sltp()` to lock SL/TP at entry instead.

Free-form tags via `.with_tag(name, values)`. UI metadata via `.with_tag_config(name, label, color)` (or via the `#[tag]` macro).

### Parameters

- `entry_long` (`Vec<bool>`, default `required`): True on bars where a long entry fires
- `exit_long` (`Vec<bool>`, default `required`): True on bars where a long position should close
- `entry_short` (`Vec<bool>`, default `required`): True on bars where a short entry fires
- `exit_short` (`Vec<bool>`, default `required`): True on bars where a short position should close

### Returns

SignalOutput (use builder methods to attach SL/TP, tags, emission mode)

### Example

```rust
SignalOutput::new(entry_long, exit_long, entry_short, exit_short)
    .with_sl_long(sl_long)
    .with_tp_long(tp_long)
    .with_sl_short(sl_short)
    .with_tp_short(tp_short)
    .with_entry_only_sltp()
    .with_tag("morning", morning_mask)
    .with_tag("trend",   trend_mask)
```

<a id="with_size_long-with_size_short"></a>
## with_size_long / with_size_short

```rust
with_size_long / with_size_short.with_size_long(Vec<f64>) / .with_size_short(Vec<f64>)
```

Per-trade dynamic position sizing.

Size at each entry tick where `entry_long[i] == true` (or `entry_short[i] == true`). Length must equal the tick count. When unset, the engine uses the UI-configured default size (current behavior).

Use cases: volatility-scaled sizing (`size = target_dollar_risk / atr[i]`), Kelly-criterion sizing, equity-fraction sizing.

### Parameters

- `sizes` (`Vec<f64>`, default `required`): Length-N vector. Read at the entry tick; ignored on non-entry ticks. NaN/Inf -> use default.

### Example

```rust
let target_risk = 100.0; // dollars
let sizes: Vec<f64> = atr.iter().map(|a| target_risk / a.max(0.5)).collect();

SignalOutput::new(entry_long, exit_long, entry_short, exit_short)
    .with_size_long(sizes.clone())
    .with_size_short(sizes)
```

### Notes

- Composes with `fill_model()`: a `VolumeWeightedFillModel` can clip the requested size based on available bid/ask depth.
- Defaults to the UI size when None or when entry slot has NaN/Inf.

<a id="with_slippage_per_tick"></a>
## with_slippage_per_tick

```rust
with_slippage_per_tick.with_slippage_per_tick(Vec<f64>)
```

Variable slippage per tick (overrides the UI scalar).

When set, the engine reads `slippage_per_tick[i]` at fill time instead of the UI scalar. Use this to model wider slippage at market open / news events / wide-spread regimes.

Length must equal the tick count. Composes with `fill_model()` - the FillModel receives the per-tick slippage in its `FillContext`.

### Parameters

- `slips` (`Vec<f64>`, default `required`): Per-tick slippage (price units). NaN -> use scalar.

### Example

```rust
// 4x slippage in the 30-second window around 8:30 ET news drops
let mut slips = vec![scalar; n];
let news_start = ts_for("08:29:30");
let news_end   = ts_for("08:30:30");
for i in 0..n {
    if data.timestamp[i] >= news_start && data.timestamp[i] <= news_end {
        slips[i] = scalar * 4.0;
    }
}

SignalOutput::new(...).with_slippage_per_tick(slips)
```

<a id="without_sl_ratchet"></a>
## without_sl_ratchet

```rust
without_sl_ratchet.without_sl_ratchet()
```

Disable the auto-ratchet on SL (longs only rise, shorts only fall).

Default behavior: once a long SL has been raised by the strategy, the engine clamps subsequent values so SL never drops below the running max. This protects against accidental SL widening.

Call `.without_sl_ratchet()` for chandelier-style trailing exits where the SL CAN retreat (e.g. ATR widening pulls SL further from price during a volatility spike). The engine then takes whatever the strategy emits at each tick.

### Example

```rust
// chandelier exit: SL widens with ATR
let chandelier_sl: Vec<f64> = (0..n).map(|i| close[i] - atr[i] * 3.0).collect();
SignalOutput::new(...)
    .with_sl_long(chandelier_sl)
    .without_sl_ratchet()
```

<a id="fill_model"></a>
## fill_model

```rust
fill_modelfn fill_model(&self) -> Option<Box<dyn FillModel>>
```

Optional `Strategy` method. Override the default ask/bid fill behavior.

By default the engine fills longs at `ask + slippage` and shorts at `bid - slippage` (`AskBidFillModel`). Override `fill_model()` on your `Strategy` impl to plug in a different model.

The `FillModel` trait sees a `FillContext` with bid/ask/mid/sizes/side/action and returns a `FillResult` with price, filled_size (can be partial), and effective slippage.

**Built-in implementations:**
- `AskBidFillModel { slippage: f64 }` (default).
- `MidpointFillModel { slippage: f64 }` - fills at `(bid + ask) / 2`. Useful for limit-order modeling.
- `VolumeWeightedFillModel { max_consume_pct: f64 }` - clips fill size based on available bid/ask size; partial fills are returned.

**Custom impls:** implement `FillModel for MyModel` with `fn fill(&self, ctx: &FillContext) -> FillResult`. Anything goes (limit-with-offset, volume-aware, regime-aware, etc.).

### Example

```rust
use qc_strategy_api::prelude::*;

impl Strategy for MyStrategy {
    fn fill_model(&self) -> Option<Box<dyn FillModel>> {
        Some(Box::new(VolumeWeightedFillModel { max_consume_pct: 0.10 }))
    }

    fn calculate(&self, data: &TickData, prep: &DayPrep) -> SignalOutput {
        // ... your logic ...
    }
}
```

### Notes

- Engine pre-baseline tests assert byte-identical results when `fill_model()` returns None.
- `MidpointFillModel` is the simplest non-default; great for limit-order backtests.
- `VolumeWeightedFillModel` returns partial `filled_size` when available size is below requested - integrate with `with_size_long/short` for proper sizing semantics.

<a id="sltp-emission-modes"></a>
## SL/TP emission modes

PerTick (default) re-evaluates SL/TP each tick. EntryOnly locks them at entry.

`SltpEmission::PerTick` (default): the bracket engine reads `sl_long[i]` / `tp_long[i]` on every tick of an open position. Use this for trailing stops or any pattern where the bracket should adapt mid-trade.

`SltpEmission::EntryOnly` via `.with_entry_only_sltp()`: the engine reads SL/TP only on the entry tick and ignores all subsequent values. Use this for fixed brackets (the common case).

The difference matters for performance and semantics: per-tick is more flexible but the SL/TP arrays must be filled meaningfully on every in-position tick, not just at entry.

### Example

```rust
// Trailing stop: per-tick re-evaluation
SignalOutput::new(...)
    .with_sl_long(trailing_sl)  // sl_long[i] updates each tick
    // emit_sltp defaults to PerTick

// Fixed bracket: locks at entry
SignalOutput::new(...)
    .with_sl_long(entry_sl)
    .with_tp_long(entry_tp)
    .with_entry_only_sltp()
```
