# Strategy Contract (Shape-C)

> Every new Scout strategy implements Shape-C, a Strategy class with on_bar, an injected broker, and explicit rules that prevent lookahead bias and unrealistic fills.

Source: https://finnyai.tech/docs/strategy-contract

## The Shape-C class

```python
class Strategy:
    def __init__(self, broker, params=None):
        self.broker = broker
        self.params = params or {}

    def on_bar(self, symbol: str, bar: dict) -> None:
        # bar keys: open, high, low, close, volume, timestamp, symbol
        pass
```

The runner instantiates `Strategy` once and calls `on_bar` for every bar in the backtest window or live stream. The `broker` instance is injected, you never import or build one.

## Broker API

- `buy(symbol, qty=None, notional=None)`, Place a buy order. Pass qty or notional, not both.
- `sell(symbol, qty=None, notional=None)`, Place a sell order. Same rules.
- `position(symbol)`, Signed quantity. 0 means flat, negative is short.
- `cash()`, Available cash for new orders.
- `equity()`, Cash plus mark-to-market value of open positions.
- `price(symbol)`, Last close, useful for sizing notional orders.

## Two rules that trip up new strategies

### Next-bar fill

Orders submitted inside `on_bar` fill at the *next* bar. Calling `broker.buy()` and then `broker.position()` in the same bar returns 0. Treat the position as updated only on the bar after the order.

### Lookahead rule

Only `bar["open"]` is decision-time-safe. By the time you know `high`, `low`, or `close`, the bar is over, using them to decide an entry is lookahead bias and your backtest will lie to you.

> **Compute indicators from prior closes**: Build your moving averages, RSI, Bollinger bands, etc. from the *history* of closes before the current bar, never from the current bar's close. The engine doesn't enforce this for you; the responsibility lives in the strategy.

## Parameters

Read parameters from `self.params` with sensible defaults, that way a parameter sweep (see `/backtest sweep`) can actually vary them. Hard-coded constants make sweeps return identical metrics.

```python
def on_bar(self, symbol, bar):
    rsi_period = self.params.get("rsi_period", 14)
    oversold = self.params.get("oversold", 30)
    # ...
```

## Shape auto-detection

The backtest runner inspects the module to figure out which contract is in use:

- A `Strategy` class whose `__init__` takes `broker` as its first parameter → **Shape C** (current).
- A legacy class name → **Shape A**: routed through a v1 compatibility adapter.
- Neither → the runner aborts with a hint pointing at this page.

The **Shape-C loader** also accepts simpler strategy formats and maps them into the institutional `engine_v2`, see the `/backtest` reference.
