You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Oanda API调用XAG等非外汇品种时出现price报错问题求解

Oanda API交易策略自动化XAG品种报错问题

我正在使用Oanda API实现交易策略自动化,遇到price报错问题:该报错仅在选择XAG(白银)等部分交易品种时触发,选择外汇交易对时无异常。我推测问题源于品种分类差异,但目前尚未收到Oanda官方回复。我位于英国,可使用包括CFD在内的大部分交易产品。若有相关解决经验恳请分享。

相关实现代码

import pandas as pd
import numpy as np
import time
from datetime import datetime, timedelta
import tpqoa

class SMABollTrader(tpqoa.tpqoa):
    def __init__(self, conf_file, instrument, bar_length, SMA, dev, SMA_S, SMA_L, units):
        super().__init__(conf_file)
        self.instrument = instrument
        self.bar_length = pd.to_timedelta(bar_length)
        self.tick_data = pd.DataFrame()
        self.raw_data = None
        self.data = None 
        self.last_bar = None
        self.units = units
        self.position = 0
        self.profits = []
        self.price = []
        
        # 策略专属参数
        self.SMA = SMA
        self.dev = dev
        self.SMA_S = SMA_S
        self.SMA_L = SMA_L
 
    def get_most_recent(self, days = 5):
        while True:
            time.sleep(2)
            now = datetime.utcnow()
            now = now - timedelta(microseconds = now.microsecond)
            past = now - timedelta(days = days)
            df = self.get_history(instrument = self.instrument, start = past, end = now,
                                   granularity = "S5", price = "M", localize = False).c.dropna().to_frame()
            df.rename(columns = {"c":self.instrument}, inplace = True)
            df = df.resample(self.bar_length, label = "right").last().dropna().iloc[:-1]
            self.raw_data = df.copy()
            self.last_bar = self.raw_data.index[-1]
            if pd.to_datetime(datetime.utcnow()).tz_localize("UTC") - self.last_bar < self.bar_length:
                break
            
    def on_success(self, time, bid, ask):
        print(self.ticks, end = " ")
        
        recent_tick = pd.to_datetime(time)
        df = pd.DataFrame({self.instrument:(ask + bid)/2}, 
                          index = [recent_tick])
        self.tick_data = pd.concat([self.tick_data, df])
        
        if recent_tick - self.last_bar > self.bar_length:
            self.resample_and_join()
            self.define_strategy()
            self.execute_trades()

    def resample_and_join(self):
        self.raw_data = pd.concat([self.raw_data, self.tick_data.resample(self.bar_length, 
                                                          label="right").last().ffill().iloc[:-1]])
        self.tick_data = self.tick_data.iloc[-1:]
        self.last_bar = self.raw_data.index[-1]

    def define_strategy(self):
        df = self.raw_data.copy()
        
        df["SMA"] = df[self.instrument].rolling(self.SMA).mean()
        df["Lower"] = df["SMA"] - df[self.instrument].rolling(self.SMA).std() * self.dev
        df["Upper"] = df["SMA"] + df[self.instrument].rolling(self.SMA).std() * self.dev
        df["distance"] = df[self.instrument] - df.SMA
        df["SMA_S"] = df[self.instrument].rolling(self.SMA_S).mean()
        df["SMA_L"] = df[self.instrument].rolling(self.SMA_L).mean()
        
        # 原逻辑语法修正
        df["position"] = np.where((df[self.instrument] < df.Lower) & (df["SMA_S"] > df["SMA_L"]), 1, np.nan)
        df["position"] = np.where((df[self.instrument] > df.Upper) & (df["SMA_S"] < df["SMA_L"]), -1, df["position"])
        df["position"] = np.where(df.distance * df.distance.shift(1) < 0, 0, df["position"])
        df["position"] = df.position.ffill().fillna(0)
        
        self.data = df.copy()   
    
    def execute_trades(self):
        if self.data["position"].iloc[-1] == 1:
            if self.position in [0, None]:
                order = self.create_order(self.instrument, self.units, suppress = True, ret = True)
                self.report_trade(order, "GOING LONG")
            elif self.position == -1:
                order = self.create_order(self.instrument, self.units * 2, suppress = True, ret = True) 
                self.report_trade(order, "GOING LONG")
            self.position = 1
        elif self.data["position"].iloc[-1] == -1: 
            if self.position == 0:
                order = self.create_order(self.instrument, -self.units, suppress = True, ret = True)
                self.report_trade(order, "GOING SHORT")
            elif self.position == 1:
                order = self.create_order(self.instrument, -self.units * 2, suppress = True, ret = True)
                self.report_trade(order, "GOING SHORT")
            self.position = -1
        elif self.data["position"].iloc[-1] == 0: 
            if self.position == -1:
                order = self.create_order(self.instrument, self.units, suppress = True, ret = True)
                self.report_trade(order, "GOING NEUTRAL")
            elif self.position == 1:
                order = self.create_order(self.instrument, -self.units, suppress = True, ret = True) 
                self.report_trade(order, "GOING NEUTRAL")
            self.position = 0

    def report_trade(self, order, going):
        time = order["time"]
        units = order["units"]
        price = order["price"]
        pl = float(order["pl"])
        self.profits.append(pl)
        cumpl = sum(self.profits)
        print("\n" + 100* "-")
        print("{} | {}".format(time, going))
        print("{} | units = {} | price = {} | P&L = {} | Cum P&L = {}".format(time, units, price, pl, cumpl))
        print(100 * "-" + "\n")  

if __name__ == "__main__":
    trader = SMABollTrader("oanda.cfg", "EUR_GBP", "15m", SMA = 82, dev = 4, SMA_S = 38, SMA_L = 135, units = 100000)
    trader.get_most_recent()
    trader.stream_data(trader.instrument, stop = None )
    if trader.position != 0: # 若存在最终未平仓头寸
        close_order = trader.create_order(trader.instrument, units = -trader.position * trader.units,
        suppress = True, ret = True)
        trader.report_trade(close_order, "GOING NEUTRAL")
        trader.signal = 0

修复方案

  • 修正品种命名:Oanda平台的白银交易代码不是XAG,英国地区可交易的白银CFD标准代码为XAG_USD,你可以调用get_instruments()接口拉取你的账户可交易的全量品种列表,确认对应贵金属的准确命名后再传入参数,外汇对命名规则简单无歧义所以不会触发该类错误。
  • 替换price参数:贵金属等非外汇品种部分时段不支持price="M"(中间价)的历史数据查询,可将get_history方法中的price="M"修改为price="B"(买价)或price="A"(卖价),也可以同时拉取买价卖价后自行计算中间价,避免无中间价数据触发的报错。
  • 调整下单单位:外汇对的最小交易单位通常为1,而XAG的最小交易单位为1盎司,你当前代码中默认的100000单位远高于XAG的单笔最大下单限制,可通过get_instruments()接口查询对应品种的最小交易量、精度参数后再设置units值。
  • 处理非连续交易时段:外汇是24小时连续交易,而贵金属存在固定休市时段,你resample时间序列时会出现空值,可在resample后增加dropna()操作过滤空K线,避免后续指标计算异常。
  • 修正多条件判断语法:原代码中np.where() and np.where()的写法完全错误,会返回不符合预期的数组格式,波动更高的贵金属数据更容易触发后续计算报错,上述代码中已修正为&连接多条件的标准写法。

内容的提问来源于stack exchange,提问作者Bigali33

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 19:48:03