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数据科学库启发的时间序列表。
更多关于Golang自动化外汇交易引擎 - 支持Oanda经纪商的算法回测与实盘交易的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于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线回测虽然计算量大,但能更真实地模拟市场环境。建议继续完善测试覆盖,特别是针对边界条件和并发场景的测试。

