Golang自动化外汇交易引擎 - 支持Oanda经纪商的算法回测与实盘交易

Golang自动化外汇交易引擎 - 支持Oanda经纪商的算法回测与实盘交易 今天,我开源了我的外汇回测/交易引擎。它可以通过下载数千根K线数据,并针对每一根K线运行交易算法,从而在历史市场数据上运行交易策略。该引擎的目标是让回测和实盘交易的运行方式近乎一致,这也是为什么在模拟中算法会针对每一根K线运行。我发现许多其他交易引擎都是一次性对所有K线运行算法。

考虑到对于每一根K线,在做出交易决策时几乎都需要计算到某个限制之前的所有历史K线,这意味着每秒需要进行数百万甚至数千万次计算,因此Go语言是开发此类程序的绝佳选择。在我的M1 MacBook Pro上,即使完全不使用多线程,一个大规模的回测也只需几秒钟即可完成。

本项目采用0BSD许可证。您可以随意使用它。欢迎提交PR和修复bug。

目前存在许多bug,并且我承认在编写软件测试用例方面做得不够好,因此很难确定其实际有效性。我主要是为未来的开发奠定了基础。backtest.go文件中的代码也相当混乱,非常欢迎进行重构!

以下内容摘自项目的README(如果听起来像推销,那是因为它来自我的LinkedIn):

Autotrader是我用Go语言在两周内开发的一个外汇量化交易引擎。其独特的回测模拟功能,允许用户设计的交易算法在历史市场数据上运行。该模拟考虑了经纪商手续费、对冲、杠杆、市价单、限价单、止损单等。一旦识别出可靠的策略,用户就可以在他们实盘的经纪商账户上运行他们创建的交易算法。

Autotrader在从交易策略的实现,到经纪商通过其JSON REST API维护的订单和头寸的整个流程中,都插入了抽象层。本项目使用的所有金融算法和数据结构都是从头开发的,包括一个受NumPy Python数据科学库启发的时间序列表。

GitHub - lukewilson2002/autotrader: Forex automated trading algorithm backtesting and...


更多关于Golang自动化外汇交易引擎 - 支持Oanda经纪商的算法回测与实盘交易的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang自动化外汇交易引擎 - 支持Oanda经纪商的算法回测与实盘交易的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个非常出色的项目!将回测引擎设计为逐K线处理确实能更好地模拟实盘交易环境,避免了传统批量回测中常见的未来数据偏差问题。Go语言的高并发特性和性能优势在这个场景下得到了充分体现。

以下是针对项目核心功能的技术分析和示例代码:

1. 核心架构分析

您的逐K线处理架构更符合实际交易逻辑。这里展示一个典型的事件驱动回测循环:

// 简化版回测引擎核心逻辑
func (e *Engine) RunBacktest(strategy Strategy, data []Candle) (*Results, error) {
    portfolio := NewPortfolio()
    broker := NewSimulatedBroker()
    
    for i, candle := range data {
        // 获取历史数据上下文
        historicalData := data[:i+1]
        
        // 更新指标计算
        indicators := CalculateIndicators(historicalData)
        
        // 运行交易策略
        signals := strategy.Analyze(candle, indicators, portfolio)
        
        // 执行订单
        for _, signal := range signals {
            order := broker.PlaceOrder(signal)
            portfolio.ProcessOrder(order, candle.Close)
        }
        
        // 更新持仓盈亏
        portfolio.UpdatePositions(candle)
    }
    
    return portfolio.GetResults(), nil
}

2. 性能优化建议

针对您提到的高频计算需求,这里有一些Go特有的优化技巧:

// 使用sync.Pool重用对象减少GC压力
var candlePool = sync.Pool{
    New: func() interface{} {
        return &Candle{}
    },
}

// 预分配切片避免动态扩容
func PreallocateIndicators(dataSize int) []Indicator {
    indicators := make([]Indicator, 0, dataSize)
    return indicators
}

// 并行计算指标(当策略允许时)
func CalculateIndicatorsParallel(data []Candle) map[string]float64 {
    var wg sync.WaitGroup
    results := make(chan IndicatorResult, len(data))
    
    for _, candle := range data {
        wg.Add(1)
        go func(c Candle) {
            defer wg.Done()
            // 并行计算每个指标
            result := calculateRSI(c)
            results <- result
        }(candle)
    }
    
    wg.Wait()
    close(results)
    
    return aggregateResults(results)
}

3. Oanda API集成示例

针对您支持的Oanda经纪商,这里是一个实际的API调用示例:

type OandaBroker struct {
    client    *http.Client
    accountID string
    apiKey    string
    baseURL   string
}

func (o *OandaBroker) PlaceMarketOrder(instrument string, units float64) (*OrderResponse, error) {
    url := fmt.Sprintf("%s/v3/accounts/%s/orders", o.baseURL, o.accountID)
    
    order := map[string]interface{}{
        "order": map[string]interface{}{
            "type":        "MARKET",
            "instrument":  instrument,
            "units":       strconv.FormatFloat(units, 'f', -1, 64),
            "timeInForce": "FOK",
        },
    }
    
    body, _ := json.Marshal(order)
    req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
    req.Header.Set("Authorization", "Bearer "+o.apiKey)
    req.Header.Set("Content-Type", "application/json")
    
    resp, err := o.client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var result OrderResponse
    json.NewDecoder(resp.Body).Decode(&result)
    
    return &result, nil
}

4. 时间序列数据结构

受NumPy启发的时序数据实现:

type TimeSeries struct {
    data     []float64
    timestamps []time.Time
    mu       sync.RWMutex
}

func (ts *TimeSeries) SMA(period int) []float64 {
    ts.mu.RLock()
    defer ts.mu.RUnlock()
    
    if len(ts.data) < period {
        return nil
    }
    
    result := make([]float64, len(ts.data)-period+1)
    for i := period - 1; i < len(ts.data); i++ {
        sum := 0.0
        for j := 0; j < period; j++ {
            sum += ts.data[i-j]
        }
        result[i-period+1] = sum / float64(period)
    }
    
    return result
}

func (ts *TimeSeries) Apply(f func(float64) float64) {
    ts.mu.Lock()
    defer ts.mu.Unlock()
    
    for i := range ts.data {
        ts.data[i] = f(ts.data[i])
    }
}

5. 回测结果验证

为确保回测准确性,建议添加以下验证逻辑:

func ValidateBacktest(backtestResults, liveResults *Results) bool {
    // 检查关键指标的一致性
    metrics := []string{"SharpeRatio", "MaxDrawdown", "WinRate"}
    
    for _, metric := range metrics {
        backtestValue := backtestResults.GetMetric(metric)
        liveValue := liveResults.GetMetric(metric)
        
        // 允许10%的偏差
        if math.Abs(backtestValue-liveValue)/backtestValue > 0.1 {
            return false
        }
    }
    
    return true
}

这个项目的架构设计非常合理,特别是在处理高频计算时充分利用了Go的优势。逐K线回测虽然计算量大,但能更真实地模拟市场环境。建议继续完善测试覆盖,特别是针对边界条件和并发场景的测试。

回到顶部