BackNRun

BackNRun is a powerful, flexible trading bot framework written in Go. It provides a comprehensive set of tools for developing, backtesting, and optimizing trading strategies for cryptocurrency markets.


π Roadmap & Features
- Backtesting: Test strategies against historical data to evaluate performance
- Strategy Development: Create custom trading strategies using a simple, extensible interface
- Notifications - Telegram Notifications: Implemented notifications from Telegram channel
- Parameter Optimization: Find optimal parameters for your strategies using grid search or random search
- Performance Metrics: Analyze strategy performance with comprehensive metrics
- Web dashboard for live tracking: Plot trading results with indicators and trades
π Architecture
BackNRun is built with a modular architecture that separates concerns and allows for easy extension:
π backnrun/
βββ π cmd/ # Command-line application
βββ π examples/ # Examples of usage
βββ π images/ # Preview images
βββ π internal/ # Internal packages
βββ π core/ # Core interfaces and types
βββ π exchange/ # Exchange implementations
βββ π indicator/ # Technical indicators
βββ π logger/ # Logging utilities
βββ π metric/ # Performance metrics
βββ π notification/ # Notification systems
βββ π optimizer/ # Strategy parameter optimization
βββ π order/ # Order management
βββ π plot/ # Visualization tools
βββ π storage/ # Data storage
βββ π scripts/ # Scripts utilities
βββ π strategies/ # Presets strategies
βββ π bot/ # Bot implementations
βββ π strategy/ # Strategy implementations
π¦ Installation
Prerequisites
Building from Source
# Clone the repository
git clone https://github.com/raykavin/backnrun.git
cd backnrun
# Build the application
go build -o backnrun cmd/backnrun/main.go
β‘ Quick Start
Downloading Historical Data
# Download 30 days of BTC/USDT 15-minute candles
./backnrun download -p BTCUSDT -t 15m -d 30 -o btc-15m.csv
Running a Backtest
Create a Go file with your backtest configuration:
package main
import (
"context"
"github.com/raykavin/backnrun/bot"
"github.com/raykavin/backnrun/strategies"
"github.com/raykavin/backnrun/core"
"github.com/raykavin/backnrun/exchange"
"github.com/raykavin/backnrun/plot"
"github.com/raykavin/backnrun/storage"
)
func main() {
// Set up context and logging
ctx := context.Background()
log :=bot.DefaultLog
log.SetLevel(core.DebugLevel)
// Initialize trading strategy
strategy := strategies.NewCrossEMA(9, 21, 10.0)
// Configure trading pairs
settings := &core.Settings{
Pairs: []string{"BTCUSDT"},
}
// Initialize data feed
dataFeed, err := exchange.NewCSVFeed(
strategy.Timeframe(),
exchange.PairFeed{
Pair: "BTCUSDT",
File: "btc-15m.csv",
Timeframe: "15m",
},
)
if err != nil {
log.Fatal(err)
}
// Initialize in-memory storage
db, err := storage.FromMemory()
if err != nil {
log.Fatal(err)
}
// Initialize paper wallet
wallet := exchange.NewPaperWallet(
ctx,
"USDT",
log,
exchange.WithPaperAsset("USDT", 1000),
exchange.WithDataFeed(dataFeed),
)
// Initialize chart
chart, err := plot.NewChart(
log,
plot.WithStrategyIndicators(strategy),
plot.WithPaperWallet(wallet),
)
if err != nil {
log.Fatal(err)
}
// Set up the trading bot
bot, err := bot.NewBot(
ctx,
settings,
wallet,
strategy,
log,
bot.WithBacktest(wallet),
bot.WithStorage(db),
bot.WithCandleSubscription(chart),
bot.WithOrderSubscription(chart),
)
if err != nil {
log.Fatal(err)
}
// Run simulation
if err := bot.Run(ctx); err != nil {
log.Fatal(err)
}
// Display results
bot.Summary()
// Show interactive chart
if err := chart.Start(); err != nil {
log.Fatal(err)
}
}
Run the backtest:
go run backtest.go
π€ Available Strategies
BackNRun comes with several example strategies:
- EMA Cross: Trading based on exponential moving average crossovers
- MACD Divergence: Trading based on MACD indicator divergence
- Triple EMA Cross: Trading using three exponential moving averages
- Trend Master: Trend-following strategy with multiple indicators
- Turtle Trading: Implementation of the famous Turtle Trading system
- Larry Williams 91: Based on Larry Williams' trading methodology
- Trailing Stop: Strategy with dynamic trailing stop-loss
- OCO Sell: One-Cancels-the-Other order strategy
Creating Custom Strategies
To create a custom strategy, implement the core.Strategy interface:
type Strategy interface {
// Timeframe is the time interval in which the strategy will be executed. eg: 1h, 1d, 1w
Timeframe() string
// WarmupPeriod is the necessary time to wait before executing the strategy, to load data for indicators.
// This time is measured in the period specified in the `Timeframe` function.
WarmupPeriod() int
// Indicators will be executed for each new candle, in order to fill indicators before `OnCandle` function is called.
Indicators(df *Dataframe) []ChartIndicator
// OnCandle will be executed for each new candle, after indicators are filled, here you can do your trading logic.
// OnCandle is executed after the candle close.
OnCandle(df *Dataframe, broker Broker)
}
Example of a simple strategy:
type MyStrategy struct {
// Strategy parameters
fastPeriod int
slowPeriod int
}
func NewMyStrategy() *MyStrategy {
return &MyStrategy{
fastPeriod: 9,
slowPeriod: 21,
}
}
func (s *MyStrategy) Timeframe() string {
return "1h"
}
func (s *MyStrategy) WarmupPeriod() int {
return 100
}
func (s *MyStrategy) Indicators(df *core.Dataframe) []core.ChartIndicator {
// Calculate indicators
df.Metadata["fast"] = indicator.EMA(df.Close, s.fastPeriod)
df.Metadata["slow"] = indicator.EMA(df.Close, s.slowPeriod)
// Return chart indicators for visualization
return []core.ChartIndicator{
{
Overlay: true,
GroupName: "Moving Averages",
Time: df.Time,
Metrics: []core.IndicatorMetric{
{
Values: df.Metadata["fast"],
Name: "Fast EMA",
Color: "red",
Style: core.StyleLine,
},
{
Values: df.Metadata["slow"],
Name: "Slow EMA",
Color: "blue",
Style: core.StyleLine,
},
},
},
}
}
func (s *MyStrategy) OnCandle(ctx context.Context, df *core.Dataframe, broker core.Broker) {
// Get current position
assetPosition, quotePosition, err := broker.Position(df.Pair)
if err != nil {
return
}
// Get indicator values
fast := df.Metadata["fast"].Last(0)
slow := df.Metadata["slow"].Last(0)
// Buy signal: fast EMA crosses above slow EMA
if fast > slow && df.Metadata["fast"].Last(1) <= df.Metadata["slow"].Last(1) && quotePosition > 0 {
// Calculate position size
price := df.Close.Last(0)
amount := quotePosition / price
// Create market buy order
broker.CreateOrderMarket(core.SideTypeBuy, df.Pair, amount)
}
// Sell signal: fast EMA crosses below slow EMA
if fast < slow && df.Metadata["fast"].Last(1) >= df.Metadata["slow"].Last(1) && assetPosition > 0 {
// Create market sell order
broker.CreateOrderMarket(core.SideTypeSell, df.Pair, assetPosition)
}
}
π Parameter Optimization
BackNRun includes a powerful parameter optimization package that helps you find the best parameters for your trading strategies. The optimizer supports multiple algorithms and performance metrics.
Optimization Algorithms
- Grid Search: Exhaustively tests all combinations of parameter values within specified ranges
- Random Search: Tests random combinations of parameter values, which can be more efficient for high-dimensional parameter spaces
The optimizer tracks various performance metrics:
profit: Total profit
win_rate: Percentage of winning trades
payoff: Payoff ratio (average win / average loss)
profit_factor: Profit factor (gross profit / gross loss)
sqn: System Quality Number
drawdown: Maximum drawdown
sharpe_ratio: Sharpe ratio
trade_count: Total number of trades
Example: Optimizing a Strategy
package main
import (
"context"
"fmt"
"time"
"github.com/raykavin/backnrun/bot"
"github.com/raykavin/backnrun/strategies"
"github.com/raykavin/backnrun/core"
"github.com/raykavin/backnrun/exchange"
"github.com/raykavin/backnrun/optimizer"
)
func main() {
// Set up context and logging
ctx := context.Background()
log := bot.DefaultLog
log.SetLevel(core.InfoLevel)
// Initialize data feed for backtesting
dataFeed, err := exchange.NewCSVFeed(
"15m",
exchange.PairFeed{
Pair: "BTCUSDT",
File: "btc-15m.csv",
Timeframe: "15m",
},
)
if err != nil {
log.Fatal(err)
}
// Configure trading pairs
settings := &core.Settings{
Pairs: []string{"BTCUSDT"},
}
// Create strategy factory for EMA Cross strategy
strategyFactory := func(params optimizer.ParameterSet) (core.Strategy, error) {
// Extract parameters with validation
emaLength, ok := params["emaLength"].(int)
if !ok {
return nil, fmt.Errorf("emaLength must be an integer")
}
smaLength, ok := params["smaLength"].(int)
if !ok {
return nil, fmt.Errorf("smaLength must be an integer")
}
minQuoteAmount, ok := params["minQuoteAmount"].(float64)
if !ok {
return nil, fmt.Errorf("minQuoteAmount must be a float")
}
// Create and return the strategy
return strategies.NewCrossEMA(emaLength, smaLength, minQuoteAmount), nil
}
// Create evaluator
evaluator := optimizer.NewBacktestStrategyEvaluator(
strategyFactory,
settings,
dataFeed,
log,
1000.0, // Starting balance
"USDT", // Quote currency
)
// Define parameters for optimization
parameters := []optimizer.Parameter{
{
Name: "emaLength",
Description: "Length of the EMA indicator",
Default: 9,
Min: 3,
Max: 50,
Step: 1,
Type: optimizer.TypeInt,
},
{
Name: "smaLength",
Description: "Length of the SMA indicator",
Default: 21,
Min: 5,
Max: 100,
Step: 5,
Type: optimizer.TypeInt,
},
{
Name: "minQuoteAmount",
Description: "Minimum quote currency amount for trades",
Default: 10.0,
Min: 1.0,
Max: 100.0,
Step: 5.0,
Type: optimizer.TypeFloat,
},
}
// Configure optimizer
config := optimizer.NewConfig().
WithParameters(parameters...).
WithMaxIterations(50).
WithParallelism(4).
WithLogger(log).
WithTargetMetric(optimizer.MetricProfit, true)
// Create grid search optimizer
gridSearch, err := optimizer.NewGridSearch(config)
if err != nil {
log.Fatal(err)
}
// Run optimization
fmt.Println("Starting grid search optimization...")
startTime := time.Now()
results, err := gridSearch.Optimize(
ctx,
evaluator,
optimizer.MetricProfit,
true,
)
if err != nil {
log.Fatal(err)
}
duration := time.Since(startTime)
fmt.Printf("Optimization completed in %s\n", duration.Round(time.Second))
// Print results
optimizer.PrintResults(results, optimizer.MetricProfit, 5)
// Save results to CSV
outputFile := "ema_optimization_results.csv"
if err := optimizer.SaveResultsToCSV(results, optimizer.MetricProfit, outputFile); err != nil {
log.Errorf("Failed to save results: %v", err)
} else {
fmt.Printf("Results saved to %s\n", outputFile)
}
}
π Live Trading
To use BackNRun for live trading, you need to configure it with a real exchange:
package main
import (
"context"
"github.com/raykavin/backnrun/bot"
"github.com/raykavin/backnrun/strategies"
"github.com/raykavin/backnrun/core"
"github.com/raykavin/backnrun/exchange/binance"
"github.com/raykavin/backnrun/notification"
)
func main() {
// Set up context and logging
ctx := context.Background()
log :=bot.DefaultLog
log.SetLevel(core.InfoLevel)
// Initialize trading strategy
strategy := strategies.NewCrossEMA(9, 21, 10.0)
// Configure trading pairs
settings := &core.Settings{
Pairs: []string{"BTCUSDT"},
}
// Initialize Binance exchange
exchange, err := binance.NewExchange(ctx, log, binance.Config{
Type: binance.MarketTypeSpot,
ApiKey: "YOUR_API_KEY",
ApiSecret: "YOUR_API_SECRET",
})
if err != nil {
log.Fatal(err)
}
// Initialize Telegram notifications (optional)
telegramNotifier, err := notification.NewTelegram(
"YOUR_TELEGRAM_BOT_TOKEN",
"YOUR_TELEGRAM_CHAT_ID",
log,
)
if err != nil {
log.Fatal(err)
}
// Set up the trading bot
bot, err := bot.NewBot(
ctx,
settings,
exchange,
strategy,
log,
bot.WithTelegram(telegramNotifier),
)
if err != nil {
log.Fatal(err)
}
// Run the bot
if err := bot.Run(ctx); err != nil {
log.Fatal(err)
}
}
π€ Contributing
Contributions to BackNRun are welcome! Here are some ways you can help improve the project:
- Report bugs and suggest features by opening issues on GitHub
- Submit pull requests with bug fixes or new features
- Improve documentation to help other users and developers
- Share your custom strategies with the community
π License
BackNRun is distributed under the GNU General Public License v3.0.
For complete license terms and conditions, see the LICENSE file in the repository.
Copyright Β© Raykavin Meireles
For support, collaboration, or questions about BackNRun:
Email: raykavin.meireles@gmail.com
GitHub: @raykavin
LinkedIn: @raykavin.dev
Instagram: @raykavin.dev