Backtest price-action and quant strategies in Python — simple API, serious simulation.
pip install stolgo · Python 3.10+ · MIT License · bandl data & brokers
What is stolgo?
stolgo is a lightweight backtesting framework built for traders who think in price action — breakouts, consolidation, support/resistance, candle behaviour — not only indicator crossovers.
Most libraries optimise for MACD/RSI grids. stolgo optimises for clear rules on OHLCV, a deterministic event loop (fills, cash, positions), and reporting you can share (equity curve, drawdown, trade log, Plotly tearsheet).
Write your logic once. Point it at market data or a CSV. Get metrics and charts.
Why stolgo?
| Strength | What you get |
|---|---|
| Price-action first | Write pa.crosses_above(pa.resistance(21)) — readable rules, not indicator soup |
| Composable | Combine levels, relations, candles, and streaks with & | ~ .then() |
| Trade in one line | trade.long(ctx, rr=(1, 2), stop="candle_low") — stops, targets, sizing handled |
| Presets | pa.preset.consolidation_breakout(7) and friends — proven setups, zero wiring |
| Honest simulation | Event loop with configurable fill timing (next_open or close), commission, slippage |
| No look-ahead | ctx.data only exposes history up to the current bar; MTF levels align safely |
| Data your way | bandl for crypto/equity OHLCV, or load() for CSV/Parquet |
| Built-in analytics | Sharpe, drawdown, hit rate, profit factor, HTML tearsheet |
Installation
pip install stolgo
For live market data via bandl (recommended):
pip install stolgo bandl
Optional extras:
pip install "stolgo[numba]" # faster sweeps
pip install "stolgo[ui]" # local read-only backtest browser
See docs/UI.md for the local backtest UI, its read-only API, and security notes.
Price action in 30 seconds
This is the heart of stolgo. Describe a setup the way you'd say it out loud, attach a
risk/reward bracket, and backtest it. One import: import stolgo.pa as pa.
from datetime import datetime, timedelta, timezone
import stolgo.pa as pa
from stolgo import Backtest, Bandl, Context, Strategy, trade
# 1. Describe the edge in plain price-action language
entry = pa.crosses_above(pa.resistance(21)) # close breaks the 21-bar high
# 2. Trade it with a 1:2 risk/reward bracket, stop under the signal candle
class Breakout(Strategy):
bracket = None
def on_bar(self, ctx: Context) -> None:
if not ctx.position.flat: # in a trade → manage exit
if trade.bracket_hit(ctx, self.bracket): # stop or target touched
trade.close(ctx)
self.bracket = None
elif entry(ctx): # flat + breakout → enter
self.bracket = trade.long(ctx, rr=(1, 2), stop="candle_low", qty=0.05)
# 3. Run it on real data
end = datetime.now(timezone.utc)
df = Bandl().history("BTCUSDT", "1h", end - timedelta(days=365), end)
result = Backtest(Breakout(), df, fill_on="close").run()
print(result.summary())
result.report.to_html("tearsheet.html")
That's a complete, look-ahead-safe breakout backtest with stops, targets, fees, and a shareable tearsheet — no indicators required.
The price-action vocabulary
Everything below is a flat attribute on pa. Mix and match with & (and), | (or),
~ (not), and .then() (sequence) to build any setup.
Levels — a price line per bar
pa.resistance(20) pa.support(20) # rolling highs / lows
pa.range_high(7) pa.range_low(7) # consolidation box edges
pa.donchian_high(20) pa.donchian_low(20)
pa.vwap() pa.prev_day_high() pa.prev_day_low()
pa.swing_high(5) pa.swing_low(5) pa.pivot_point()
pa.level(42_000) # a fixed price
pa.resistance(21, tf="1d") # daily level on intraday bars (MTF)
Relations — turn a level into a true/false rule
pa.above(lvl) pa.below(lvl)
pa.crosses_above(lvl) pa.crosses_below(lvl) # breakouts / breakdowns
pa.rejected_at(lvl) pa.recovered_at(lvl) # failed break / reclaim
pa.near(lvl, pct=0.005) pa.touched(lvl)
Patterns — candles, streaks, structure, momentum
pa.bullish_engulfing() pa.bearish_engulfing() pa.hammer() pa.doji()
pa.streak.green(3) pa.streak.red(3) pa.first_red_day()
pa.consolidation(days=7) pa.breakout_up(7) pa.breakout_down(7)
pa.run_up(min_pct=2.0) pa.parabolic_up() pa.giant_uptrend()
Compose them into the setup you actually trade
# 7-day box, then close breaks the box high
entry = pa.consolidation(days=7) & pa.crosses_above(pa.range_high(7))
# three green candles, then a bearish engulfing → fade it
fade = pa.streak.green(3).then(pa.bearish_engulfing())
# break above 21-day daily resistance, but only near VWAP
intraday = pa.crosses_above(pa.resistance(21, tf="1d")) & pa.near(pa.vwap())
Trade it: risk/reward brackets (stolgo.trade)
stolgo.trade turns a signal into an order with a stop and target — no manual SL/TP math.
from stolgo import trade
trade.long(ctx, rr=(1, 2), stop="candle_low", qty=0.05) # target = 2× risk
trade.short(ctx, rr=(1, 3), stop="candle_high", qty=0.05)
trade.bracket_hit(ctx, bracket) # -> "stop", "target", or None on this bar
trade.close(ctx) # flatten (longs sell, shorts cover)
Size by fixed qty, or risk a fixed fraction of equity per trade with
size_risk_pct=0.01.
Presets: battle-tested setups in one line
Don't want to wire rules yourself? pa.preset.* returns ready-made rules.
import stolgo.pa as pa
pa.preset.scalp_green_fade(min_green=3) # fade a green run on a reversal candle
pa.preset.consolidation_breakout(days=7) # classic box breakout
pa.preset.breakout_intraday(days=7) # daily S/R, intraday trigger → (long, short)
pa.preset.failed_break_intraday(days=7) # fade failed breaks of daily S/R
pa.preset.parabolic_short(min_pct=2.0) # short the first red day after a parabola
See docs/PA.md for the full grammar, multi-timeframe levels, and every
rule. Runnable bots live under examples/pa/.
Not just price action
Prefer indicators or vector signals? The same engine runs those too.
from stolgo import Backtest, Context, Strategy, load
from stolgo.signals import sma
class Trend(Strategy):
def on_start(self, ctx: Context) -> None:
self._sma = sma(ctx.data.close, 50)
def on_bar(self, ctx: Context) -> None:
if ctx.position.flat and ctx.data.close[-1] > self._sma[ctx.i]:
ctx.buy(size_pct=0.25)
elif not ctx.position.flat and ctx.data.close[-1] < self._sma[ctx.i]:
ctx.close()
df = load("ohlcv.csv", symbol="BTCUSDT")
print(Backtest(Trend(), df, cash=100_000, commission=0.001).run().summary())
Data sources
stolgo does not lock you into one vendor. Use whichever fits your workflow.
bandl — Bandl
bandl aggregates OHLCV from crypto exchanges, equity feeds, and other providers. stolgo wraps it as Bandl: fetch, normalize columns, optional parquet cache, then backtest.
from stolgo import Bandl
df = Bandl(provider="crypto").history("BTCUSDT", "1d", start, end)
Use this for crypto (e.g. BTCUSDT), Indian/US equity, and any symbol bandl supports. Configure credentials per bandl’s docs when required.
Local files — load
For research archives, exports, or offline work:
from stolgo import load
df = load("data/btc_daily.parquet", symbol="BTCUSDT")
Supports .csv, .parquet, .pq. Timestamps are parsed to UTC; columns are normalized to open, high, low, close, volume.
Bring your own DataFrame
Already have pandas OHLCV? Pass it directly to Backtest as long as it has a UTC datetime index (or a timestamp column).
Public API (v0.2)
| Import | Purpose |
|---|---|
stolgo.pa |
Price-action toolkit: levels, relations, patterns, presets |
trade |
Risk/reward brackets: trade.long, trade.short, trade.bracket_hit, trade.close |
Backtest |
High-level runner → RunResult |
Strategy |
Base class: on_start, on_bar, on_fill, on_end |
Context |
Per-bar API: ctx.data, ctx.position, ctx.buy / ctx.close |
Bandl |
Market data via bandl (BandlDataSource alias) |
load |
Load CSV / Parquet |
RunResult |
metrics, trades, equity, report |
parameter_sweep |
Grid search over strategy parameters |
stolgo.signals |
sma, ema, atr, rsi, donchian, … |
Advanced: Engine, RunConfig, Pipeline (cross-sectional), stolgo.report.export_all.
Examples
Price action (stolgo.pa + trade) — start here:
| Script | Setup |
|---|---|
examples/pa/btc_21d_sr_intraday.py |
Break of 21-day daily support/resistance on intraday bars (long + short) |
examples/pa/btc_consolidation_pa.py |
7-day consolidation breakout with a 1:4 bracket |
examples/pa/scalp_green_fade.py |
Fade a green streak on a reversal candle |
examples/pa/intraday_breakout.py |
pa.preset.breakout_intraday long/short |
examples/pa/failed_break.py |
Fade failed breaks of daily S/R |
examples/pa/parabolic_short.py |
Short the first red day after a parabolic run |
Indicators & vector signals:
| Script | Demonstrates |
|---|---|
examples/trend_breakout_backtest.py |
SMA trend, signals, tearsheet |
examples/vector_momentum_backtest.py |
Vector entries / exits (no on_bar body) |
examples/parameter_sweep.py |
100-combo parameter sweep |
CLI:
stolgo my_strategy.py --class MyStrategy --data ohlcv.csv --output ./results
How it works (short)
OHLCV (Bandl / load / DataFrame)
↓
Backtest(strategy, data).run()
↓
Engine: for each bar → match fills → on_bar → risk → submit orders
↓
RunResult: equity, trades, metrics, Plotly tearsheet
Default fill model: signal on bar t → fill at bar t+1 open (fill_on="next_open"). Use fill_on="close" when you want same-bar close fills (e.g. breakout-on-close setups).
Legacy price-action helpers
The original stolgo modules (candlestick, breakout, trend, …) remain available for pattern checks on raw DataFrames. New projects should prefer the Strategy + Backtest API above.
from stolgo.candlestick import CandleStick
from stolgo.breakout import Breakout
cs = CandleStick()
is_engulfing = cs.is_bullish_engulfing(df)
Development
git clone https://github.com/stockalgo/stolgo.git
cd stolgo
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -q
Architecture notes: docs/HLD.md.
Contributing
Pull requests are welcome. For larger changes, open an issue first. Follow PEP 8, add tests for new behaviour, and keep the public API intuitive (prefer from stolgo import … over deep internal paths).
License
Metadata
Release files for stolgo 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stolgo-0.2.0.tar.gz | 65.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stolgo-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 140.6 kB
Release files / stolgo-0.2.0.tar.gz
| Download URL | stolgo-0.2.0.tar.gz |
|---|---|
| Size | 65.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
79bd1c53825a650120412ba5dc52b118c4e66150076e03e4907e5955bee893bb
|
|
BLAKE2b-256 checksum How to use checksums |
d466eac14d2283911d6c744b122b9689880ba425186bf96e25bf1b041898c326
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency logRelease files / stolgo-0.2.0-py3-none-any.whl
| Download URL | stolgo-0.2.0-py3-none-any.whl |
|---|---|
| Size | 74.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9d6944597c890dc54c16c40a5f1ca1d957766604c967eff6bd06cef7c87cb72b
|
|
BLAKE2b-256 checksum How to use checksums |
56de880b543499b9ed10edc76b2d5628bf2e44a8892afbbeda649f400628b0b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency log