# When to Use Rust

Why TBBO tick path lives in Rust and OHLC bar path lives in Python. File layout, build cycle, performance model.

<a id="when-to-use-rust-vs-python"></a>
## When to use Rust vs Python

Pick Rust for tick-level TBBO logic and tight performance budgets. Pick Python for OHLC bar logic, REPL ergonomics, and richer ta.* coverage.

Rust .rs files run via the `qc_strategy_api` crate. They consume raw TBBO ticks (bid/ask/sizes/spread/volume/delta) and have access to a smaller but native-speed indicator library ([`ta::sma`](https://quantchartsllc.com/docs/rust/rust-helpers.md#tasma), [`ta::ema`](https://quantchartsllc.com/docs/rust/rust-helpers.md#taema), [`ta::rsi`](https://quantchartsllc.com/docs/rust/rust-helpers.md#tarsi), [`ta::macd`](https://quantchartsllc.com/docs/rust/rust-helpers.md#tamacd), [`ta::bollinger`](https://quantchartsllc.com/docs/rust/rust-helpers.md#tabollinger), [`ta::atr_bid_ask`](https://quantchartsllc.com/docs/rust/rust-helpers.md#taatr_bid_ask), etc.). They compile through Cargo and run as a worker binary spawned per parameter combination.

Python .py files run via the in-process Python executor on aggregated OHLC bars. They get the full pandas + numpy stack plus the larger `ta.*` library (sma, ema, wma, rsi, macd, stochastic, bollinger_bands, atr, stddev, highest, lowest, change, roc).

The two paths share the same chart, the same backtest engine, the same analyzer, and the same trade output schema. Strategies of either language can return the same SL/TP shape and the same tag dict.

### Notes

- Rust gives tick precision: per-tick decisions on bid/ask, microprice, spread compression, large-trade detection.
- Python gives bar-level convenience: `df['close'].rolling(20).mean()`, [`ta.bollinger_bands()`](https://quantchartsllc.com/docs/python/py-ta.md#ta-bollinger_bands), etc.
- For Python on TBBO data, the executor aggregates ticks into bars at the active timeframe before calling `calculate(self, df)`. Tick-level decisions inside Python are not supported.
- Performance: a Rust .rs file with `prepare()` doing the heavy lifting and `calculate()` a tight loop will outperform a comparable Python strategy by 10x to 100x on TBBO sweeps.

<a id="file-layout"></a>
## File layout

A Rust strategy is a single .rs file with use qc_strategy_api::prelude::*; and an attribute-macro decorator.

Skeleton:

### Example

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

#[strategy(name = "My TBBO Strategy", description = "...", columns = ["volume", "delta"])]
#[tag(name = "trend_up", label = "Trend Up", color = "#26A69A")]
#[derive(Default)]
pub struct MyStrategy {
    #[param(default = 20, min = 5, max = 200, label = "Period")]
    pub period: usize,
    #[param(default = 2.0, min = 0.5, max = 5.0, step = 0.25, label = "Mult")]
    pub multiplier: f64,
}

impl Strategy for MyStrategy {
    fn prepare(data: &TickData) -> DayPrep {
        // hoisted: param-independent, runs once per day
        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 {
        // hot path: only param-dependent work here
        let n = data.len();
        let (upper, middle, lower) = ta::bollinger(&data.mid, self.period, self.multiplier);
        let mut entry_long = vec![false; n];
        let mut entry_short = vec![false; n];
        let mut exit_long = vec![false; n];
        let mut exit_short = vec![false; n];
        for i in self.period..n {
            entry_long[i] = data.mid[i] < lower[i];
            entry_short[i] = data.mid[i] > upper[i];
            if middle[i].is_finite() {
                exit_long[i] = data.mid[i] >= middle[i];
                exit_short[i] = data.mid[i] <= middle[i];
            }
        }
        SignalOutput::new(entry_long, exit_long, entry_short, exit_short)
    }
}
```

### Notes

- The macro generates the runner main() and the JSON schema for the params panel.
- #[derive(Default)] is required: the runner builds a default instance, then writes #[param] fields from the UI.
- Custom struct fields (without #[param]) get Default::default() and act as scratch memory per combo.

<a id="performance-model"></a>
## Performance model

prepare() runs once per day, calculate() runs once per parameter combo. Hoist relentlessly.

The runner calls `prepare(data)` once per trading day before the parameter sweep starts. Anything stored on [`DayPrep`](https://quantchartsllc.com/docs/rust/rust-data.md#dayprep) is shared across all combos. Then `calculate(self, data, prep)` runs once per combo, in parallel across worker threads.

Rules of thumb for fast Rust strategies:
- Pre-allocate output Vecs with `with_capacity(n)`. Never `Vec::new()` + push in the tick loop.
- Avoid `String::clone()` in `calculate()`. Hoist labels to `&'static str`.
- Use slice indexing (`data.bid[i]`) over iterator chains in the hot path.
- Put any `ta::*` call with a literal period inside `prepare()`, not `calculate()`.
