发布OANDA REST API的Python封装vn.oanda

This commit is contained in:
chenxy123 2016-02-27 17:06:33 +08:00
parent 85ded38bfb
commit 863bb6a639
23 changed files with 752 additions and 0 deletions

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

38
vn.oanda/README.md Normal file
View File

@ -0,0 +1,38 @@
# vn.oanda
### 简介
OANDA外汇交易接口基于REST API开发实现了以下功能
1. 发送、修改、撤销委托
2. 查询委托、持仓(按照每笔成交算)、汇总持仓(按照单一货币对算)、资金、成交历史
3. 实时行情和成交推送
4. 获取Forex Lab中的日历、订单簿、历史持仓比、价差、交易商持仓、Autochartist
### 特点
相比较于[OANDA官网](http://developer.oanda.com/rest-live/sample-code/)上贴出的一些Python API如pyoanda、oanda-trading-environment等vn.oanda的一些不同
1. 面向对象的API设计接近CTP API的结构对于国内用户而言更容易上手
2. 三个独立的工作线程,分别处理:用户请求(如发送委托等)、行情推送、事件推送(如成交事件等),提供更高的性能
3. 参考CTP API的设计主动函数调用的结果通过异步回调函数的方式推送到程序中适用于开发真正可靠的实盘交易程序pyoanda里使用的同步阻塞工作模式在实盘应用中的风险想象你的交易程序发送委托请求后因为网络问题不能立即返回因此主线程阻塞导致界面卡死或者背后的策略引擎线程卡死对新的行情事件完全失去响应
### Quick Start
1. 安装Anaconda 2.7 32位
2. 前往[OANDA](http://www.oanda.com)注册一个fxTrade practice测试账户注意国家不要选中国会无法申请API token作者测试英国可以
3. 在网站登陆后进入Manage Funds记录下自己的Account Number
4. 回到上一个界面左侧有个Manage API Access在Recent Logins上方没有的就是第一步国家选错了进入后生成token
5. 下载vn.oanda到本地后打开test.py修改token和accountId为你的信息
6. 将test.py中想要测试的功能取消注释开始使用吧
### API版本
OANDA REST API

609
vn.oanda/api.py Normal file
View File

@ -0,0 +1,609 @@
# encoding: utf-8
import json
import requests
from Queue import Queue, Empty
from threading import Thread
API_SETTING = {}
API_SETTING['practice'] = {'rest': 'https://api-fxpractice.oanda.com',
'stream': 'https://stream-fxpractice.oanda.com'}
API_SETTING['trade'] = {'rest': 'https://api-fxpractice.oanda.com',
'stream': 'https://stream-fxtrade.oanda.com/'}
FUNCTIONCODE_GETINSTRUMENTS = 0
FUNCTIONCODE_GETPRICES = 1
FUNCTIONCODE_GETPRICEHISTORY = 2
FUNCTIONCODE_GETACCOUNTS = 3
FUNCTIONCODE_GETACCOUNTINFO = 4
FUNCTIONCODE_GETORDERS = 5
FUNCTIONCODE_SENDORDER = 6
FUNCTIONCODE_GETORDERINFO = 7
FUNCTIONCODE_MODIFYORDER = 8
FUNCTIONCODE_CANCELORDER = 9
FUNCTIONCODE_GETTRADES = 10
FUNCTIONCODE_GETTRADEINFO = 11
FUNCTIONCODE_MODIFYTRADE= 12
FUNCTIONCODE_CLOSETRADE = 13
FUNCTIONCODE_GETPOSITIONS = 14
FUNCTIONCODE_GETPOSITIONINFO= 15
FUNCTIONCODE_CLOSEPOSITION = 16
FUNCTIONCODE_GETTRANSACTIONS = 17
FUNCTIONCODE_GETTRANSACTIONINFO = 18
FUNCTIONCODE_GETACCOUNTHISTORY = 19
FUNCTIONCODE_GETCALENDAR = 20
FUNCTIONCODE_GETPOSITIONRATIOS = 21
FUNCTIONCODE_GETSPREADS = 22
FUNCTIONCODE_GETCOMMIMENTS = 23
FUNCTIONCODE_GETORDERBOOK = 24
FUNCTIONCODE_GETAUTOCHARTIST = 25
FUNCTIONCODE_STREAMPRICES = 26
FUNCTIONCODE_STREAMEVENTS = 27
########################################################################
class OandaApi(object):
""""""
DEBUG = False
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.token = ''
self.accountId = ''
self.headers = {}
self.restDomain = ''
self.streamDomain = ''
self.session = None
self.functionSetting = {}
self.active = False # API的工作状态
self.reqID = 0 # 请求编号
self.reqQueue = Queue() # 请求队列
self.reqThread = Thread(target=self.processQueue) # 请求处理线程
self.streamPricesThread = Thread(target=self.processStreamPrices) # 实时行情线程
self.streamEventsThread = Thread(target=self.processStreamEvents) # 实时事件线程(成交等)
#----------------------------------------------------------------------
def init(self, settingName, token, accountId):
"""初始化接口"""
self.restDomain = API_SETTING[settingName]['rest']
self.streamDomain = API_SETTING[settingName]['stream']
self.session = requests.Session()
self.token = token
self.accountId = accountId
self.headers['Authorization'] = 'Bearer ' + self.token
self.initFunctionSetting(FUNCTIONCODE_GETINSTRUMENTS, {'path': '/v1/instruments',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETPRICES, {'path': '/v1/prices',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETPRICEHISTORY, {'path': 'v1/candles',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTS, {'path': '/v1/accounts',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTINFO, {'path': '/v1/accounts/%s' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETORDERS, {'path': '/v1/accounts/%s/orders' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_SENDORDER, {'path': '/v1/accounts/%s/orders' %self.accountId,
'method': 'POST'})
self.initFunctionSetting(FUNCTIONCODE_GETORDERINFO, {'path': '/v1/accounts/%s/orders' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_MODIFYORDER, {'path': '/v1/accounts/%s/orders' %self.accountId,
'method': 'PATCH'})
self.initFunctionSetting(FUNCTIONCODE_CANCELORDER, {'path': '/v1/accounts/%s/orders' %self.accountId,
'method': 'DELETE'})
self.initFunctionSetting(FUNCTIONCODE_GETTRADES, {'path': '/v1/accounts/%s/trades' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETTRADEINFO, {'path': '/v1/accounts/%s/trades' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_MODIFYTRADE, {'path': '/v1/accounts/%s/trades' %self.accountId,
'method': 'PATCH'})
self.initFunctionSetting(FUNCTIONCODE_CLOSETRADE, {'path': '/v1/accounts/%s/trades' %self.accountId,
'method': 'DELETE'})
self.initFunctionSetting(FUNCTIONCODE_GETPOSITIONS, {'path': '/v1/accounts/%s/positions' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETPOSITIONINFO, {'path': '/v1/accounts/%s/positions' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_CLOSEPOSITION, {'path': '/v1/accounts/%s/positions' %self.accountId,
'method': 'DELETE'})
self.initFunctionSetting(FUNCTIONCODE_GETTRANSACTIONS, {'path': '/v1/accounts/%s/transactions' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETTRANSACTIONINFO, {'path': '/v1/accounts/%s/transactions' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTHISTORY, {'path': '/v1/accounts/%s/alltransactions' %self.accountId,
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETCALENDAR, {'path': '/labs/v1/calendar',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETPOSITIONRATIOS, {'path': '/labs/v1/historical_position_ratios',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETSPREADS, {'path': '/labs/v1/spreads',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETCOMMIMENTS, {'path': '/labs/v1/commitments',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETORDERBOOK, {'path': '/labs/v1/orderbook_data',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETAUTOCHARTIST, {'path': '/labs/v1/autochartist',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETAUTOCHARTIST, {'path': '/labs/v1/autochartist',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_STREAMPRICES, {'path': '/v1/prices',
'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_STREAMEVENTS, {'path': '/v1/events',
'method': 'GET'})
self.active = True
self.reqThread.start()
self.streamEventsThread.start()
self.streamPricesThread.start()
#----------------------------------------------------------------------
def exit(self):
"""退出接口"""
self.active = False
self.reqThread.join()
#----------------------------------------------------------------------
def initFunctionSetting(self, code, setting):
"""初始化API功能字典"""
self.functionSetting[code] = setting
#----------------------------------------------------------------------
def processRequest(self, req):
"""发送请求并通过回调函数推送数据结果"""
url = req['url']
method = req['method']
params = req['params']
stream = False
if 'stream' in req:
stream = req['stream']
if method in ['GET', 'DELETE']:
myreq = requests.Request(method, url, headers=self.headers, params=params)
elif method in ['POST', 'PATCH']:
myreq = requests.Request(method, url, headers=self.headers, data=params)
pre = myreq.prepare()
r = None
error = None
try:
r = self.session.send(pre, stream=stream)
except Exception, e:
error = e
return r, error
#----------------------------------------------------------------------
def processQueue(self):
"""处理请求队列中的请求"""
while self.active:
try:
req = self.reqQueue.get(block=True, timeout=1) # 获取请求的阻塞为一秒
callback = req['callback']
reqID = req['reqID']
r, error = self.processRequest(req)
if r:
try:
data = r.json()
if self.DEBUG:
print callback.__name__
callback(data, reqID)
except Exception, e:
self.onError(error)
else:
self.onError(error)
except Empty:
pass
#----------------------------------------------------------------------
def sendRequest(self, code, params, callback, optional=''):
"""发送请求"""
setting = self.functionSetting[code]
url = self.restDomain + setting['path']
if optional:
url = url + '/' + optional
self.reqID += 1
req = {'url': url,
'method': setting['method'],
'params': params,
'callback': callback,
'reqID': self.reqID}
self.reqQueue.put(req)
return self.reqID
#----------------------------------------------------------------------
def onError(self, error, reqID):
"""错误信息回调"""
print error, reqID
#----------------------------------------------------------------------
def getInstruments(self, params):
"""查询可交易的合约列表"""
return self.sendRequest(FUNCTIONCODE_GETINSTRUMENTS, params, self.onGetInstruments)
#----------------------------------------------------------------------
def onGetInstruments(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getPrices(self, params):
"""查询价格"""
return self.sendRequest(FUNCTIONCODE_GETPRICES, params, self.onGetPrices)
#----------------------------------------------------------------------
def onGetPrices(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getPriceHisory(self, params):
"""查询历史价格数据"""
return self.sendRequest(FUNCTIONCODE_GETPRICEHISTORY, params, self.onGetPriceHistory)
#----------------------------------------------------------------------
def onGetPriceHistory(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getAccounts(self):
"""查询用户的所有账户"""
return self.sendRequest(FUNCTIONCODE_GETACCOUNTS, {}, self.onGetAccounts)
#----------------------------------------------------------------------
def onGetAccounts(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getAccountInfo(self):
"""查询账户数据"""
return self.sendRequest(FUNCTIONCODE_GETACCOUNTINFO, {}, self.onGetAccountInfo)
#----------------------------------------------------------------------
def onGetAccountInfo(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getOrders(self, params):
"""查询所有委托"""
return self.sendRequest(FUNCTIONCODE_GETORDERS, params, self.onGetOrders)
#----------------------------------------------------------------------
def onGetOrders(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def sendOrder(self, params):
"""发送委托"""
return self.sendRequest(FUNCTIONCODE_SENDORDER, params, self.onSendOrder)
#----------------------------------------------------------------------
def onSendOrder(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getOrderInfo(self, optional):
"""查询委托信息"""
return self.sendRequest(FUNCTIONCODE_GETORDERINFO, {}, self.onGetOrderInfo, optional)
#----------------------------------------------------------------------
def onGetOrderInfo(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def modifyOrder(self, params, optional):
"""修改委托"""
return self.sendRequest(FUNCTIONCODE_MODIFYORDER, params, self.onModifyOrder, optional)
#----------------------------------------------------------------------
def onModifyOrder(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def cancelOrder(self, optional):
"""查询委托信息"""
return self.sendRequest(FUNCTIONCODE_CANCELORDER, {}, self.onCancelOrder, optional)
#----------------------------------------------------------------------
def onCancelOrder(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getTrades(self, params):
"""查询所有仓位"""
return self.sendRequest(FUNCTIONCODE_GETTRADES, params, self.onGetTrades)
#----------------------------------------------------------------------
def onGetTrades(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getTradeInfo(self, optional):
"""查询仓位信息"""
return self.sendRequest(FUNCTIONCODE_GETTRADEINFO, {}, self.onGetTradeInfo, optional)
#----------------------------------------------------------------------
def onGetTradeInfo(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def modifyTrade(self, params, optional):
"""修改仓位"""
return self.sendRequest(FUNCTIONCODE_MODIFYTRADE, params, self.onModifyTrade, optional)
#----------------------------------------------------------------------
def onModifyTrade(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def closeTrade(self, optional):
"""平仓"""
return self.sendRequest(FUNCTIONCODE_CLOSETRADE, {}, self.onCloseTrade, optional)
#----------------------------------------------------------------------
def onCloseTrade(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getPositions(self):
"""查询所有汇总仓位"""
return self.sendRequest(FUNCTIONCODE_GETPOSITIONS, {}, self.onGetPositions)
#----------------------------------------------------------------------
def onGetPositions(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getPositionInfo(self, optional):
"""查询汇总仓位信息"""
return self.sendRequest(FUNCTIONCODE_GETPOSITIONINFO, {}, self.onGetPositionInfo, optional)
#----------------------------------------------------------------------
def onGetPositionInfo(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def closePosition(self, optional):
"""平仓汇总仓位信息"""
return self.sendRequest(FUNCTIONCODE_CLOSEPOSITION, {}, self.onClosePosition, optional)
#----------------------------------------------------------------------
def onClosePosition(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getTransactions(self, params):
"""查询所有资金变动"""
return self.sendRequest(FUNCTIONCODE_GETTRANSACTIONS, params, self.onGetTransactions)
#----------------------------------------------------------------------
def onGetTransactions(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getTransactionInfo(self, optional):
"""查询资金变动信息"""
return self.sendRequest(FUNCTIONCODE_GETTRANSACTIONINFO, {}, self.onGetTransactionInfo, optional)
#----------------------------------------------------------------------
def onGetTransactionInfo(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getAccountHistory(self):
"""查询账户资金变动历史"""
return self.sendRequest(FUNCTIONCODE_GETACCOUNTHISTORY, {}, self.onGetAccountHistory)
#----------------------------------------------------------------------
def onGetAccountHistory(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getCalendar(self, params):
"""查询日历"""
return self.sendRequest(FUNCTIONCODE_GETCALENDAR, params, self.onGetCalendar)
#----------------------------------------------------------------------
def onGetCalendar(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getPositionRatios(self, params):
"""查询持仓比例"""
return self.sendRequest(FUNCTIONCODE_GETPOSITIONRATIOS, params, self.onGetPositionRatios)
#----------------------------------------------------------------------
def onGetPositionRatios(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getSpreads(self, params):
"""查询所有仓位"""
return self.sendRequest(FUNCTIONCODE_GETSPREADS, params, self.onGetSpreads)
#----------------------------------------------------------------------
def onGetSpreads(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getCommitments(self, params):
"""查询交易商持仓情况"""
return self.sendRequest(FUNCTIONCODE_GETCOMMIMENTS, params, self.onGetCommitments)
#----------------------------------------------------------------------
def onGetCommitments(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getOrderbook(self, params):
"""查询订单簿"""
return self.sendRequest(FUNCTIONCODE_GETORDERBOOK, params, self.onGetOrderbook)
#----------------------------------------------------------------------
def onGetOrderbook(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def getAutochartist(self, params):
"""查询Autochartist识别的模式"""
return self.sendRequest(FUNCTIONCODE_GETAUTOCHARTIST, params, self.onGetAutochartist)
#----------------------------------------------------------------------
def onGetAutochartist(self, data, reqID):
"""回调函数"""
print data, reqID
#----------------------------------------------------------------------
def onPrice(self, data):
"""行情推送"""
print data
#----------------------------------------------------------------------
def onEvent(self, data):
"""事件推送(成交等)"""
print data
#----------------------------------------------------------------------
def processStreamPrices(self):
"""获取价格推送"""
# 首先获取所有合约的代码
setting = self.functionSetting[FUNCTIONCODE_GETINSTRUMENTS]
req = {'url': self.restDomain + setting['path'],
'method': setting['method'],
'params': {'accountId': self.accountId}}
r, error = self.processRequest(req)
if r:
try:
data = r.json()
symbols = [d['instrument'] for d in data['instruments']]
except Exception, e:
self.onError(e, -1)
return
else:
self.onError(error, -1)
return
# 然后订阅所有的合约行情
setting = self.functionSetting[FUNCTIONCODE_STREAMPRICES]
params = {'accountId': self.accountId,
'instruments': ','.join(symbols)}
req = {'url': self.streamDomain + setting['path'],
'method': setting['method'],
'params': params,
'stream': True}
r, error = self.processRequest(req)
if r:
for line in r.iter_lines():
if line:
try:
msg = json.loads(line)
if self.DEBUG:
print self.onPrice.__name__
self.onPrice(msg)
except Exception, e:
self.onError(e, -1)
if not self.active:
break
else:
self.onError(error, -1)
#----------------------------------------------------------------------
def processStreamEvents(self):
"""获取事件推送"""
setting = self.functionSetting[FUNCTIONCODE_STREAMEVENTS]
req = {'url': self.streamDomain + setting['path'],
'method': setting['method'],
'params': {},
'stream': True}
r, error = self.processRequest(req)
if r:
for line in r.iter_lines():
if line:
try:
msg = json.loads(line)
if self.DEBUG:
print self.onEvent.__name__
self.onEvent(msg)
except Exception, e:
self.onError(e, -1)
if not self.active:
break
else:
self.onError(error, -1)

105
vn.oanda/test.py Normal file
View File

@ -0,0 +1,105 @@
# encoding: utf-8
from api import OandaApi
if __name__ == '__main__':
token = ''
accountId = ''
api = OandaApi()
api.DEBUG = True
api.init('practice', token, accountId)
# 获取交易合约列表,通过
#api.getInstruments({'accountId': accountId})
# 获取价格,通过
#api.getPrices({'instruments': 'EUR_USD'})
# 获取历史数据,失败
#api.getPriceHisory({'instruments': 'EUR_USD',
#'granularity': 'D',
#'candleFormat': 'midpoint',
#'count': '50'})
# 查询用户的所有账户,通过
#api.getAccounts()
# 查询账户信息,通过
#api.getAccountInfo()
# 查询委托数据,通过
#api.getOrders({})
# 发送委托,通过
#api.sendOrder({'instrument': 'EUR_USD',
#'units': '10000',
#'side': 'buy',
#'type': 'market'})
# 查询委托数据,通过
#api.getOrderInfo('123')
# 修改委托,通过
#api.modifyOrder({'units': '10000',
#'side': 'buy',
#'type': 'market'}, '123')
# 撤销委托,通过
#api.cancelOrder('123')
# 查询所有持仓,通过
#api.getTrades({})
# 查询持仓数据,通过
#api.getTradeInfo('10125150909')
# 修改持仓,通过
#api.modifyTrade({'trailingStop': '150'}, '10125150909')
# 平仓,通过
#api.closeTrade('10125150909')
# 查询汇总持仓,通过
#api.getPositions()
# 查询汇总持仓细节,通过
#api.getPositionInfo('EUR_USD')
# 平仓汇总持仓,通过
#api.closePosition('EUR_USD')
# 查询账户资金变动,通过
#api.getTransactions({})
# 查询资金变动信息,通过
#api.getTransactionInfo('10135713982')
# 查询账户变动历史部分通过某些情况下可能触发JSONDecodeError
#api.getAccountHistory()
# 查询财经日历,通过
#api.getCalendar({'period': '604800'})
# 查询历史持仓比,通过
#api.getPositionRatios({'instrument': 'EUR_USD',
#'period': '604800'})
# 查询历史价差,通过
#api.getSpreads({'instrument': 'EUR_USD',
#'period': '604800'})
# 查询交易商持仓,通过
#api.getCommitments({'instrument': 'EUR_USD'})
# 查询订单簿,通过
#api.getOrderbook({'instrument': 'EUR_USD',
#'period': '604800'})
# 查询Autochartist失败OANDA服务器报错
#api.getAutochartist({'instrument': 'EUR_USD'})
# 阻塞
input()