# Strategy: define trading logic



Subclass `openbacktesting.Strategy` and implement `init()` and `next()`. Pass the class to `Backtest`; the framework creates instances. A strategy normally computes indicators once and makes decisions on each bar.

[Strategy](https://docs.openbacktesting.com/reference/strategy/#openbacktesting-strategy) · [BarState](https://docs.openbacktesting.com/reference/strategy/#openbacktesting-barstate) · [StrategyLog](https://docs.openbacktesting.com/reference/strategy/#openbacktesting-logs-strategylog)

## A complete strategy

```python
from openbacktesting import Backtest, Strategy, inputs
from openbacktesting.test import GOOG

class EmaCross(Strategy):
    fast_length = inputs.int(10, title="Fast length", minval=1)
    slow_length = inputs.int(30, title="Slow length", minval=2)

    def init(self):
        self.fast = self.ta.ema(self.close, length=self.fast_length)
        self.slow = self.ta.ema(self.close, length=self.slow_length)

    def next(self):
        if len(self.close) < 2:
            return
        if not (self.fast.valid[-2:].all() and self.slow.valid[-2:].all()):
            return
        if self.ta.is_crossover(self.fast, self.slow) and not self.position:
            self.buy(qty=1, tag="cross-up")
        elif self.ta.is_crossunder(self.fast, self.slow) and self.position:
            self.position.close()

result = Backtest(GOOG, EmaCross, end_policy="close_at_last_close").run()
print(result["# Trades"])
```

## Lifecycle and visible data

| Method | When it runs | What to do there |
| --- | --- | --- |
| `init()` | Once, with the full data history available | Register `self.ta`/`self.ind` indicators and static plots; initialize your own state. Do not place orders or calculate signals with future data. |
| `on_start()` | Once before the bar loop | Optional setup or logging; no trading. |
| `next()` | Every reached bar, including the first | Read the current prefix, check indicator validity, place/cancel orders, inspect positions. |
| `on_order(event)` | Batched order-status notifications | Inspect `event.snapshot.order_id`, `event.snapshot.status`, and `event.snapshot.reason`. New orders cannot execute recursively in that notification. |
| `on_fill(fill)` | After an actual execution | Inspect `fill.price`, `fill.qty`, and fees; a submitted order alone does not imply a fill. |
| `on_finish(summary)` | At termination, including errors | Inspect completion/reason/error or log a summary; no trading. |

`next()` is **not delayed until indicators are ready**. Check the channel's `valid[-1]` before consuming it; check length before `[-2]`. `init()` can see the future, so use trailing calculations there and retain handles, not precomputed future decisions.

See [account snapshots and events](https://docs.openbacktesting.com/reference/events/index.md) for a complete callback example and all returned fields.

## Data and state you use most often

| Attribute | Usage |
| --- | --- |
| `self.close[-1]`, `self.high[-1]`, etc. | Current bar values; `[-2]` is the previous bar. `self.close` is price data, not a close-position method. |
| `self.data_index[-1]` | Current timestamp or index value. |
| `self.bar_index` | Zero-based bar position; use `self.bar_state.is_first/is_last` for boundaries. |
| `self.data`, `self.df` | Additional columns and the currently visible DataFrame; see [data](https://docs.openbacktesting.com/reference/data/index.md). |
| `self.equity`, `self.account` | Current account value and a frozen balance/margin snapshot. |
| `self.position` | Aggregate exposure; close with `self.position.close()` or `self.close_position()`. |
| `self.orders` | Pending orders; call an order's `cancel()` to withdraw it. |
| `self.trades`, `self.closed_trades` | Open lots and closed fragments. Use `trade.sl`/`trade.tp` to change protection. |
| `self.closed_trade_stats` | Running closed-fragment counts and net profit, without rescanning all closed trades. |
| `self.instrument`, `self.timeframe` | Instrument rules and optional declared timeframe (`None` without context). |
| `self.log`, `self.plotting` | [Logs](https://docs.openbacktesting.com/reference/strategy/#strategylog) and [visual declarations](https://docs.openbacktesting.com/reference/plotting/index.md). |

## Place and manage orders

`self.buy(qty=1)` and `self.sell(qty=1)` return an `Order`. You may instead specify `amount=1000` or `percent=20` (20% of equity); these are mutually exclusive incremental sizes. All prices (`limit`, `stop`, `sl`, `tp`) are absolute prices. See [orders](https://docs.openbacktesting.com/reference/trading/index.md) for every parameter, exits, target positions, OCO, and execution timing.

## Register custom calculations

```python
def init(self):
    self.mean = self.ind(
        lambda series, length: series.rolling(length).mean(),
        self.close.s, 20, name="Custom mean",
    )

def next(self):
    if self.mean.valid[-1]:
        self.log.info(f"Mean: {self.mean[-1]:.2f}")
```

These methods belong inside your subclass. `ind(func, *args, name=None, **kwargs)` calls `func` during `init()` and wraps its result in an `IndicatorHandle`. Return a Series/DataFrame with exactly the market-data index, or a positional array of matching length. A DataFrame supplies multiple channels. Keep calculations causal; negative shifts introduce future information. Use `self.ta` for built-in formulas and see [indicator handles](https://docs.openbacktesting.com/reference/indicator-api/index.md) for indexing and multi-output results.

<a id="openbacktesting-strategy"></a>



## Strategy

Strategy base class: Register indicators in init and place orders from currently visible history in next.

### Attributes

| Attribute | Meaning |
| --- | --- |
| <a id="openbacktesting-strategy-close"></a>`close` | Close-price series for the current run, usable directly with self.ta in init. |
| <a id="openbacktesting-strategy-closed-trades"></a>`closed_trades` | Completed trades for individual-trade analysis. |
| <a id="openbacktesting-strategy-data"></a>`data` | Currently visible original market-data entry point; full history in init and only the current prefix in next. |
| <a id="openbacktesting-strategy-df"></a>`df` | Currently visible original market-data table; no guarantee of deep copying or content isolation. |
| <a id="openbacktesting-strategy-equity"></a>`equity` | Current account equity, including cash and position value. |
| <a id="openbacktesting-strategy-high"></a>`high` | High-price series for the current run. |
| <a id="openbacktesting-strategy-hl2"></a>`hl2` | (High + Low) / 2, computed lazily per run. |
| <a id="openbacktesting-strategy-hlc3"></a>`hlc3` | (High + Low + Close) / 3。 |
| <a id="openbacktesting-strategy-hlcc4"></a>`hlcc4` | (High + Low + Close + Close) / 4, excluding Open. |
| <a id="openbacktesting-strategy-low"></a>`low` | Low-price series for the current run. |
| <a id="openbacktesting-strategy-open"></a>`open` | Open-price series for the current run; [-1] is the current value. |
| <a id="openbacktesting-strategy-orders"></a>`orders` | Orders awaiting execution; cancel may be called on these orders. |
| <a id="openbacktesting-strategy-pip"></a>`pip` | Retains full-sample decimal-precision estimation; does not represent an exchange minimum step. |
| <a id="openbacktesting-strategy-position"></a>`position` | Current net-position aggregate, supporting size/profit reads and closure requests. |
| <a id="openbacktesting-strategy-ta"></a>`ta` | Indicator entry point for the current run, such as ta.ema(length=20) or ta.macd().histogram. |
| <a id="openbacktesting-strategy-trades"></a>`trades` | Currently held trades, distinct from settled closed_trades. |
| <a id="openbacktesting-strategy-volume"></a>`volume` | Volume series; missing Volume remains NaN. |
| <a id="openbacktesting-strategy-instrument"></a>`instrument` | Asset rules for this run. |
| <a id="openbacktesting-strategy-account"></a>`account` | Frozen snapshot of current balance, equity, actual fees, and margin. |
| <a id="openbacktesting-strategy-plotting"></a>`plotting` | Pure plotting-data entry point for the current run; declare in init and write during runtime callbacks. |
| <a id="openbacktesting-strategy-timeframe"></a>`timeframe` | This chart's timeframe; None without low-level context, with no inference from market data. |
| <a id="openbacktesting-strategy-data-index"></a>`data_index` | Currently visible index; saved pandas objects do not update with the cursor. |
| <a id="openbacktesting-strategy-bar-index"></a>`bar_index` | Current zero-based data position; unavailable in init, on_finish, and after the run ends. |
| <a id="openbacktesting-strategy-bar-state"></a>`bar_state` | Frozen first/last snapshot for the current data position; early termination does not relabel the current position as the last bar. |
| <a id="openbacktesting-strategy-log"></a>`log` | Structured logs for the current run; use info, warning, and error inside author callbacks. |
| <a id="openbacktesting-strategy-closed-trade-stats"></a>`closed_trade_stats` | Read frozen statistics for closed portions, including allocated fees and excluding unrealized profit or loss. |

<a id="openbacktesting-strategy-ind"></a>



### Strategy.ind

```python
Strategy.ind(func, *args, name=None, **kwargs)
```

Compute custom indicators in bulk during initialization and return read-only handles read through the backtest cursor.

<a id="openbacktesting-strategy-buy"></a>



### Strategy.buy

```python
Strategy.buy(
    *,
    qty=...,
    amount=...,
    percent=...,
    limit=None,
    stop=None,
    sl=None,
    tp=None,
    tag=None,
    reduce_only=False,
    expires_at=None,
    valid_bars=None,
)
```

Submit an incremental order; choose one of qty/amount/percent, where percent=20 means 20% of equity.

<a id="openbacktesting-strategy-init"></a>



### Strategy.init

```python
Strategy.init()
```

Strategy initialization entry point for precomputation and indicator registration; complete historical data is accessible.

<a id="openbacktesting-strategy-next"></a>



### Strategy.next

```python
Strategy.next()
```

Per-bar strategy callback, starting at input bar 0 and called once for each normally reached bar.

<a id="openbacktesting-strategy-sell"></a>



### Strategy.sell

```python
Strategy.sell(
    *,
    qty=...,
    amount=...,
    percent=...,
    limit=None,
    stop=None,
    sl=None,
    tp=None,
    tag=None,
    reduce_only=False,
    expires_at=None,
    valid_bars=None,
)
```

Submit an incremental order; choose one of qty/amount/percent, where percent=20 means 20% of equity.

<a id="openbacktesting-strategy-order-target-value"></a>



### Strategy.order_target_value

```python
Strategy.order_target_value(
    *,
    target,
    limit=None,
    stop=None,
    sl=None,
    tp=None,
    tag=None,
    reduce_only=False,
    expires_at=None,
    valid_bars=None,
)
```

Convert target net notional to quantity using Close at request time; explicit conditions match quantity targets.

<a id="openbacktesting-strategy-order-target-quantity"></a>



### Strategy.order_target_quantity

```python
Strategy.order_target_quantity(
    *,
    target,
    limit=None,
    stop=None,
    sl=None,
    tp=None,
    tag=None,
    reduce_only=False,
    expires_at=None,
    valid_bars=None,
)
```

Generate a difference order from the actual net position; always validate conditions and reuse identical active targets.

<a id="openbacktesting-strategy-order-target-percent"></a>



### Strategy.order_target_percent

```python
Strategy.order_target_percent(
    *,
    target,
    limit=None,
    stop=None,
    sl=None,
    tp=None,
    tag=None,
    reduce_only=False,
    expires_at=None,
    valid_bars=None,
)
```

Determine target net notional from equity percentage at request time; no continuous rebalancing or guaranteed fill weight.

<a id="openbacktesting-strategy-on-finish"></a>



### Strategy.on_finish

```python
Strategy.on_finish(summary)
```

Receive a frozen finish summary, including exceptional termination; trading is prohibited.

<a id="openbacktesting-strategy-close-position"></a>



### Strategy.close_position

```python
Strategy.close_position(portion=None)
```

Exit each lot; return an empty tuple when flat, while self.close remains market data.

<a id="openbacktesting-strategy-on-start"></a>



### Strategy.on_start

```python
Strategy.on_start()
```

One notification after initialization completes; order submission is prohibited here.

<a id="openbacktesting-strategy-on-fill"></a>



### Strategy.on_fill

```python
Strategy.on_fill(fill)
```

Receive actual fill facts; callbacks do not recursively trigger matching.

<a id="openbacktesting-strategy-submit-oco"></a>



### Strategy.submit_oco

```python
Strategy.submit_oco(*, first, second)
```

Atomically submit two entry requests; validate both first, prioritize first, and return a queryable group.

<a id="openbacktesting-strategy-on-order"></a>



### Strategy.on_order

```python
Strategy.on_order(event)
```

Receive frozen order events; new requests execute no earlier than the next bar.

<a id="openbacktesting-barstate"></a>



## BarState

First/last snapshot at the current execution position; saved values do not change with the run cursor.

| Attribute | Type | Meaning |
| --- | --- | --- |
| <a id="openbacktesting-barstate-is-first"></a>`is_first` | `bool` | True on the first input bar. |
| <a id="openbacktesting-barstate-is-last"></a>`is_last` | `bool` | True on the final input bar. |

## Write logs

Call `self.log.info("entry submitted")`, `.warning(...)`, or `.error(...)` with one string. Read them with `bt.get_logs()` after running. Records include bar/time and source location. Logging does not itself change order state or raise a strategy error.

<a id="openbacktesting-logs-strategylog"></a>



## StrategyLog

Read-only strategy log handle, accessed through self.log without author construction.

<a id="openbacktesting-logs-strategylog-info"></a>



### StrategyLog.info

```python
StrategyLog.info(message)
```

Record informational messages, preserving empty strings, Chinese text, and newlines.

<a id="openbacktesting-logs-strategylog-warning"></a>



### StrategyLog.warning

```python
StrategyLog.warning(message)
```

Record warning messages without changing strategy execution.

<a id="openbacktesting-logs-strategylog-error"></a>



### StrategyLog.error

```python
StrategyLog.error(message)
```

Record error-level messages; authors must explicitly raise to stop the strategy.

[Backtest](https://docs.openbacktesting.com/reference/core/index.md) · [Strategy parameters](https://docs.openbacktesting.com/reference/inputs/index.md) · [User manual](https://docs.openbacktesting.com/index.md)
