ADD: added first version
This commit is contained in:
96
cmd/backtest/main.go
Normal file
96
cmd/backtest/main.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"alpaca-bot/internal/alpaca"
|
||||
"alpaca-bot/internal/backtest"
|
||||
"alpaca-bot/internal/config"
|
||||
"alpaca-bot/internal/logger"
|
||||
"alpaca-bot/internal/strategy"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
_ = godotenv.Load()
|
||||
log := logger.New("[BACKTEST]")
|
||||
|
||||
cfg := config.LoadAlpaca()
|
||||
|
||||
// CLI flags
|
||||
symbol := flag.String("symbol", "AAPL", "Stock symbol")
|
||||
tf := flag.String("tf", "1Day", "Timeframe")
|
||||
startStr := flag.String("start", "2023-01-01", "Start date")
|
||||
endStr := flag.String("end", "2026-01-01", "End date")
|
||||
cash := flag.Float64("cash", 10000, "Start capital")
|
||||
risk := flag.Float64("risk", 0.02, "Risk per trade (fraction)")
|
||||
atrPeriod := flag.Int("atr", 14, "ATR period")
|
||||
atrMult := flag.Float64("atrMult", 1.5, "ATR multiplier for stop")
|
||||
tpMult := flag.Float64("tpMult", 2.0, "Take profit multiplier")
|
||||
fast := flag.Int("fast", 12, "Fast EMA")
|
||||
slow := flag.Int("slow", 26, "Slow EMA")
|
||||
out := flag.String("out", "output/backtest.xlsx", "Excel output path")
|
||||
flag.Parse()
|
||||
|
||||
start, err := time.Parse("2006-01-02", *startStr)
|
||||
if err != nil {
|
||||
log.Fatal("invalid start date:", err)
|
||||
}
|
||||
end, err := time.Parse("2006-01-02", *endStr)
|
||||
if err != nil {
|
||||
log.Fatal("invalid end date:", err)
|
||||
}
|
||||
|
||||
// Create output folder
|
||||
if err := os.MkdirAll(filepath.Dir(*out), 0755); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Load historical bars from Alpaca
|
||||
client := alpaca.NewClient(cfg)
|
||||
bars, err := client.GetHistoricalBars(*symbol, *tf, start, end)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Loaded %d bars for %s", len(bars), *symbol)
|
||||
|
||||
// Initialize EMA crossover strategy
|
||||
strat := strategy.NewEMACross(*fast, *slow)
|
||||
|
||||
// Initialize backtest engine with ATR stops and TakeProfit
|
||||
engine := backtest.NewEngine(*symbol, strat, *cash, *risk, *atrPeriod, *atrMult, *tpMult)
|
||||
engine.Run(bars)
|
||||
|
||||
log.Printf("Backtest finished | Trades: %d | End Cash: %.2f", len(engine.Trades), engine.Cash)
|
||||
|
||||
// Export trades to Excel
|
||||
f := excelize.NewFile()
|
||||
sheet := "Backtest"
|
||||
f.NewSheet(sheet)
|
||||
|
||||
headers := []string{"Time", "Symbol", "Side", "Price", "Qty", "Cash", "StopPrice", "TakeProfit"}
|
||||
for i, h := range headers {
|
||||
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
|
||||
f.SetCellValue(sheet, cell, h)
|
||||
}
|
||||
|
||||
for i, t := range engine.Trades {
|
||||
row := i + 2
|
||||
values := []interface{}{t.Time.Format("2006-01-02 15:04:05"), t.Symbol, t.Side, t.Price, t.Qty, t.Cash, t.StopPrice, t.TakeProfit}
|
||||
for colIdx, val := range values {
|
||||
cell, _ := excelize.CoordinatesToCellName(colIdx+1, row)
|
||||
f.SetCellValue(sheet, cell, val)
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.SaveAs(*out); err != nil {
|
||||
log.Fatal("failed to save Excel:", err)
|
||||
}
|
||||
|
||||
log.Printf("Backtest Excel saved to %s", *out)
|
||||
}
|
||||
Reference in New Issue
Block a user