The Strategy interface
Every Banger strategy is a Python class that subclasses banger.Strategy. The class declares what the strategy is (venues, universe, risk envelope) and implements how it reacts to events.
Required class attributes
| Attribute | Type | What it does |
|---|---|---|
name | str | URL-safe slug. Identifies this strategy across CLI/dashboard/MCP. |
venues | list[Venue] | Execution venues. The runtime subscribes to ticks for each. |
universe | Universe | Declarative spec of which markets to subscribe to (use signals.*). |
risk | RiskEnvelope | Position-size, open-positions, daily-loss limits. Enforced before orders ship. |
Event handlers
Implement only the handlers you need. They can be sync or async — the runtime auto-awaits coroutines.
class MyStrategy(Strategy):
name = "my_strategy"
venues = [Polymarket()]
universe = signals.polymarket_markets()
risk = RiskEnvelope(max_position_usd=Decimal("100"))
def on_start(self, ctx):
"""Called once when the deployment starts."""
ctx.log("up and running")
def on_stop(self, ctx):
"""Called once when the deployment stops (manual or auto)."""
pass
async def on_market_tick(self, market, tick, ctx):
"""Called for every price/state update on every subscribed market."""
...
def on_event(self, event, ctx):
"""Catch-all for non-tick events (resolutions, news, etc.)."""
passThe Context object
Every handler receives a ctx with the runtime APIs.
Placing orders
# Market order — fills at the latest seen tick
await ctx.place(self.venues[0], market, side="yes", size=Decimal("10"))
# Limit order — uses the explicit price
await ctx.place(
self.venues[0], market,
side="no", size=Decimal("10"), price=Decimal("0.45"),
)The side argument can be a string ("yes", "no", "buy", "sell") or a Side enum. For binary markets, the runtime auto-infers the YES price from NO (and vice versa) when only one side has ticked.
Risk gating
if ctx.can_open_position():
# the risk envelope permits a new position right now
...ctx.place() also enforces the envelope automatically — if an order would breach a limit, it raises RiskViolation and the runtime logs it.
Sizing helpers
# Quarter-Kelly stake sized against current account equity
size_usd = ctx.kelly(edge=0.05, fraction=0.25)Inspecting state
# All open positions on this deployment
for pos in ctx.positions:
print(pos.market_id, pos.side, pos.size, pos.avg_price)
# Are we paper or live?
ctx.is_paper # → True / FalseLogging
ctx.log("found edge", market_id=market.market_id, edge=0.07)
# → surfaces to your dashboard + Sentry/Better StackSurfaced parameters
Class-level scalar attributes (int, float, str, bool) are automatically discovered as “declared parameters” and shown as form fields in the marketplace cloner UX. So users who clone your strategy can configure these values without editing code.
class MyStrategy(Strategy):
name = "my_strategy"
...
# These show up as configurable inputs in the cloner UI:
edge_threshold: float = 0.05
max_holding_hours: int = 24
only_political_markets: bool = FalseWhat you don’t do
- You don’t open websockets — the runtime does that.
- You don’t track positions yourself — the runtime does.
- You don’t spawn tasks — the runtime drives the event loop.
- You don’t handle reconnects — the runtime handles that.
- You don’t handle authentication — venue creds come from env.
Your job is the alpha; ours is the plumbing.