Event-Driven Architecture
Wisp uses an event-driven model where each strategy owns its execution loop and emits signals asynchronously.
Instead of waiting for a framework callback, your strategy decides when to evaluate markets and when to act.
Core Flow
Start(ctx)launches your strategy runner in a goroutine.run(ctx)controls timing with tickers/channels.- Strategy reads market data and indicators.
- Strategy emits signals with
Emit(). Stop(ctx)shuts everything down cleanly.
This keeps strategy logic explicit, testable, and easy to reason about.
Why This Model Works
- Deterministic timing: You choose intervals and trigger conditions.
- Non-blocking execution: Signal emission does not block strategy analysis.
- Clear ownership: Lifecycle is managed in your strategy, not hidden callbacks.
- Scales cleanly: Multiple strategies and assets can run in parallel.
Minimal Pattern
func (s *MyStrategy) Start(ctx context.Context) error {
go s.run(ctx)
return nil
}
func (s *MyStrategy) run(ctx context.Context) {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// analyze -> decide -> emit
signal := s.w.Spot().Signal(s.name).
BuyMarket(s.pair, connector.Binance, decimal.NewFromFloat(0.1)).
Build()
s.w.Emit(signal)
}
}
}
Lifecycle Guidelines
- Keep
Start(ctx)non-blocking. - Respect
ctx.Done()in every select loop. - Always release resources (
ticker.Stop(), channel closures where needed). - Use status/log outputs to make decisions observable.