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.
- 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
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
- Go 1.23 or higher
- Git
# Clone the repository
git clone https://github.com/raykavin/backnrun.git
cd backnrun
# Build the application
go build -o backnrun cmd/backnrun/main.go# Download 30 days of BTC/USDT 15-minute candles
./backnrun download -p BTCUSDT -t 15m -d 30 -o btc-15m.csvCreate 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.goBackNRun 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
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)
}
}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.
- 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 profitwin_rate: Percentage of winning tradespayoff: Payoff ratio (average win / average loss)profit_factor: Profit factor (gross profit / gross loss)sqn: System Quality Numberdrawdown: Maximum drawdownsharpe_ratio: Sharpe ratiotrade_count: Total number of trades
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)
}
}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)
}
}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
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

