Your First Python Paper Strategy: Observe Before Placing Orders
Start by checking that your strategy receives the market data you expect. A no-order observer logs one structured observation from an actual market callback. It helps isolate data handling before you add decisions or simulated order logic.
This walkthrough builds an observer and distinguishes delivered data from execution evidence. A single observation can help inspect the callback and logging path. It cannot establish continuous coverage, simulated fill behavior or returns.
Why start with an observer
Feed handling can fail because a price uses unexpected units, a side is confused, or a timestamp is interpreted incorrectly. An observer helps reveal those mistakes without calling any order method.
For binary Kalshi contracts, the raw orderbook endpoint returns YES and NO bids. The complementary side determines the corresponding ask price. Current documented fixed-point fields include yes_dollars and no_dollars inside orderbook_fp. These are venue response fields, not the normalized attributes passed to a Banger strategy.
Keep the raw venue response separate from Banger's normalized Tick. The SDK uses Decimal prices, optional bid and ask values, and a datetime timestamp. Missing quotes should remain visible as missing values; do not turn them into zero or assume the raw timestamp unit applies to the SDK object.
The contract: observe, do not trade
A Banger strategy subclasses Strategy and declares its venues and universe. The market callback is on_market_tick(self, market, tick, ctx). Use ctx.log(message, **fields) for structured logging. The observer below sets a flag after its first observation and has no calls to place, close, or cancel.
This example is checked against the public Banger 0.1.7 wheel and its bundled runtime source. The CLI documentation covers account setup; inspect the installed SDK signature without connecting to a venue:
pip install bangertrades==0.1.7
python -c "import inspect; from banger import Strategy, Tick; print(inspect.signature(Strategy.on_market_tick)); print(Tick.__annotations__)"Replace REPLACE_WITH_CURRENT_OPEN_TICKER with the exact ticker of an open Kalshi market you have selected. An expired ticker cannot prove current market-data delivery. Save the following as strategy.py. It logs once, including explicit nulls for missing bid or ask values:
from banger import Strategy, signals
from banger_adapter_kalshi import Kalshi
class MarketObserver(Strategy):
name = "market-observer"
venues = [Kalshi()]
universe = signals.kalshi_markets(
status="open",
tickers=["REPLACE_WITH_CURRENT_OPEN_TICKER"],
)
def on_start(self, ctx):
self._observed = False
def on_market_tick(self, market, tick, ctx):
if self._observed:
return
self._observed = True
spread = (
tick.ask - tick.bid
if tick.bid is not None and tick.ask is not None
else None
)
ctx.log(
"market_observation",
market_id=market.market_id,
bid=str(tick.bid) if tick.bid is not None else None,
ask=str(tick.ask) if tick.ask is not None else None,
spread=str(spread) if spread is not None else None,
timestamp=tick.timestamp.isoformat(),
)
# No order method is called.
For the local CLI path, configure your Banger account as described in the CLI documentation before running banger run strategy.py --paper. This creates a local paper record in the control plane and reads public market data; it does not send orders to Kalshi. Stop with Ctrl+C after a short supervised check, for example five minutes. This article's code was tested offline with fixture ticks, not by starting a production session.
Read the log like a checklist
Inspect the one observation against the selected market. This example intentionally suppresses subsequent logs; if you need to study updates over time, deliberately extend it with a bounded sampling procedure.
- Identity: market_id must match your selected open contract.
- Units: Banger prices are Decimal values; compare them with the venue's documented units.
- Missing data: a null bid or ask indicates unavailable quote information, not a zero-priced offer.
- Time: the normalized timestamp is a datetime; compare it with your observation time to check freshness.
- Scope: one log proves a callback was delivered. It does not establish continuous feed coverage or execution behavior.
A valid observation provides limited evidence that the selected market callback and logging path work. A broader feed or trading acceptance needs its own procedure.
What this test cannot prove
An observer does not test fills. It has no order call, so zero orders and fills are expected. It does not show whether a hypothetical order would execute at the displayed price or size, nor does it measure fees, slippage, or returns.
A displayed quote describes the book at an observed time. Paper fills depend on the simulator and available data; they are research evidence, not a guarantee of venue execution or profitability.
Banger’s public product is a paper research beta. The local observer requires no venue trading keys and does not enable live trading. Its first log can contain missing or invalid quote observations; inspect those values rather than treating the log as validated data. Preserve the version and verify shutdown. Adding order logic requires a separate bounded experiment.