From 81e43a1c9c0bc7903c3c2e068a7a77effcfa1e07 Mon Sep 17 00:00:00 2001 From: lyic Date: Sun, 6 Mar 2016 14:08:38 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E6=94=B9readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9867a375..faa37068 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ --- ### 联系作者 -作者知乎名:用python的trader,想要联系作者可以通过知乎私信 +作者知乎名:用python的交易员,想要联系作者可以通过知乎私信 --- ### License From 6aa67354a9a692042c1382aa1a6a0bae9694ba6a Mon Sep 17 00:00:00 2001 From: lyic Date: Sun, 6 Mar 2016 18:09:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0vn.trader=E7=9A=84oandaGa?= =?UTF-8?q?teway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vn.oanda/test.py | 2 +- vn.oanda/{api.py => vnoanda.py} | 4 +- vn.trader/ContractData.vt | Bin 24576 -> 49152 bytes vn.trader/oandaGateway/OANDA_connect.json | 5 + vn.trader/oandaGateway/__init__.py | 0 vn.trader/oandaGateway/oandaGateway.py | 456 ++++++++++++++++ vn.trader/oandaGateway/vnoanda.py | 608 ++++++++++++++++++++++ vn.trader/uiMainWindow.py | 10 + vn.trader/vtConstant.py | 2 + vn.trader/vtEngine.py | 7 + 10 files changed, 1091 insertions(+), 3 deletions(-) rename vn.oanda/{api.py => vnoanda.py} (99%) create mode 100644 vn.trader/oandaGateway/OANDA_connect.json create mode 100644 vn.trader/oandaGateway/__init__.py create mode 100644 vn.trader/oandaGateway/oandaGateway.py create mode 100644 vn.trader/oandaGateway/vnoanda.py diff --git a/vn.oanda/test.py b/vn.oanda/test.py index 4031a0aa..e86559bb 100644 --- a/vn.oanda/test.py +++ b/vn.oanda/test.py @@ -1,6 +1,6 @@ # encoding: utf-8 -from api import OandaApi +from vnoanda import OandaApi if __name__ == '__main__': diff --git a/vn.oanda/api.py b/vn.oanda/vnoanda.py similarity index 99% rename from vn.oanda/api.py rename to vn.oanda/vnoanda.py index 9746c9ef..a53eafe9 100644 --- a/vn.oanda/api.py +++ b/vn.oanda/vnoanda.py @@ -230,9 +230,9 @@ class OandaApi(object): print callback.__name__ callback(data, reqID) except Exception, e: - self.onError(error) + self.onError(str(e), reqID) else: - self.onError(error) + self.onError(error, reqID) except Empty: pass diff --git a/vn.trader/ContractData.vt b/vn.trader/ContractData.vt index 0d5cad639355b48e2bd73403211b02a01cd095c2..9d9298eae66ee6db14ad811e8d0b22efa7ab92ba 100644 GIT binary patch literal 49152 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 OY?EIPHOqPo_4q#sw!i5B delta 26 icmZo@U~V|TI6;AtX`|v}`Hc-N@)H|sHnTYVkp}>TC<;FS diff --git a/vn.trader/oandaGateway/OANDA_connect.json b/vn.trader/oandaGateway/OANDA_connect.json new file mode 100644 index 00000000..21dd2cd9 --- /dev/null +++ b/vn.trader/oandaGateway/OANDA_connect.json @@ -0,0 +1,5 @@ +{ + "token": "请在OANDA网站申请", + "accountId": "请在OANDA网站申请", + "settingName": "practice" +} \ No newline at end of file diff --git a/vn.trader/oandaGateway/__init__.py b/vn.trader/oandaGateway/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vn.trader/oandaGateway/oandaGateway.py b/vn.trader/oandaGateway/oandaGateway.py new file mode 100644 index 00000000..6ac3ce11 --- /dev/null +++ b/vn.trader/oandaGateway/oandaGateway.py @@ -0,0 +1,456 @@ +# encoding: UTF-8 + +''' +vn.oanda的gateway接入 + +由于OANDA采用的是外汇做市商的交易模式,因此和国内接口方面有若干区别,具体如下: + +* 行情数据反映的是OANDA的报价变化,因此只有买卖价,而没有成交价 + +* OANDA的持仓管理分为单笔成交持仓(Trade数据,国内没有) + 和单一资产汇总持仓(Position数据) + +* OANDA系统中的所有时间都采用UTC时间(世界协调时,中国是UTC+8) + +* 由于采用的是外汇做市商的模式,用户的限价委托当价格被触及时就会立即全部成交, + 不会出现部分成交的情况,因此委托状态只有已报、成交、撤销三种 + +* 外汇市场采用24小时交易,因此OANDA的委托不像国内收盘后自动失效,需要用户指定 + 失效时间,本接口中默认设置为24个小时候失效 +''' + + +import os +import json +import datetime + +from vnoanda import OandaApi +from vtGateway import * + +# 价格类型映射 +priceTypeMap = {} +priceTypeMap[PRICETYPE_LIMITPRICE] = 'limit' +priceTypeMap[PRICETYPE_MARKETPRICE] = 'market' +priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} + +# 方向类型映射 +directionMap = {} +directionMap[DIRECTION_LONG] = 'buy' +directionMap[DIRECTION_SHORT] = 'sell' +directionMapReverse = {v: k for k, v in directionMap.items()} + + +######################################################################## +class OandaGateway(VtGateway): + """OANDA接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName='OANDA'): + """Constructor""" + super(OandaGateway, self).__init__(eventEngine, gatewayName) + + self.api = Api(self) + + self.qryEnabled = False # 是否要启动循环查询 + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + # 载入json文件 + fileName = self.gatewayName + '_connect.json' + fileName = os.getcwd() + '\\oandaGateway\\' + fileName + + try: + f = file(fileName) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + token = str(setting['token']) + accountId = str(setting['accountId']) + settingName = str(setting['settingName']) + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 初始化接口 + self.api.init(settingName, token, accountId) + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'接口初始化成功' + self.onLog(log) + + # 查询信息 + self.api.qryInstruments() + self.api.qryOrders() + self.api.qryTrades() + + # 初始化并启动查询 + self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.api.sendOrder_(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.api.cancelOrder_(cancelOrderReq) + + #---------------------------------------------------------------------- + def qryAccount(self): + """查询账户资金""" + self.api.getAccountInfo() + + #---------------------------------------------------------------------- + def qryPosition(self): + """查询持仓""" + self.api.getPositions() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.api.exit() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.qryAccount, self.qryPosition] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 2 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + + +######################################################################## +class Api(OandaApi): + """OANDA的API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(Api, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.orderDict = {} # 缓存委托数据 + + #---------------------------------------------------------------------- + def onError(self, error, reqID): + """错误信息回调""" + err = VtErrorData() + err.gatewayName = self.gatewayName + err.errorMsg = error + self.gateway.onError(err) + + #---------------------------------------------------------------------- + def onGetInstruments(self, data, reqID): + """回调函数""" + if not 'instruments' in data: + return + l = data['instruments'] + for d in l: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['instrument'] + contract.name = d['displayName'] + contract.exchange = EXCHANGE_OANDA + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.priceTick = float(d['pip']) + contract.size = 1 + contract.productClass = PRODUCT_FOREX + self.gateway.onContract(contract) + + self.writeLog(u'交易合约信息查询完成') + + #---------------------------------------------------------------------- + def onGetAccountInfo(self, data, reqID): + """回调函数""" + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = str(data['accountId']) + account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) + + account.available = data['marginAvail'] + account.margin = data['marginUsed'] + account.closeProfit = data['realizedPl'] + account.positionProfit = data['unrealizedPl'] + account.balance = data['balance'] + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onGetOrders(self, data, reqID): + """回调函数""" + if not 'orders' in data: + return + l = data['orders'] + + for d in l: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['instrument'] + order.exchange = EXCHANGE_OANDA + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID = str(d['id']) + + order.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) + order.offset = OFFSET_NONE + order.status = STATUS_NOTTRADED # OANDA查询到的订单都是活动委托 + + order.price = d['price'] + order.totalVolume = d['units'] + order.orderTime = getTime(d['time']) + + order.vtOrderID = '.'.join([self.gatewayName , order.orderID]) + + self.gateway.onOrder(order) + + self.orderDict[order.orderID] = order + + self.writeLog(u'委托信息查询完成') + + #---------------------------------------------------------------------- + def onGetPositions(self, data, reqID): + """回调函数""" + if not 'positions' in data: + return + l = data['positions'] + + for d in l: + pos = VtPositionData() + pos.gatewayName = self.gatewayName + + pos.symbol = d['instrument'] + pos.exchange = EXCHANGE_OANDA + pos.vtSymbol = '.'.join([pos.symbol, pos.exchange]) + pos.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) + pos.position = d['units'] + pos.price = d['avgPrice'] + pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) + + self.gateway.onPosition(pos) + + #---------------------------------------------------------------------- + def onGetTransactions(self, data, reqID): + """回调函数""" + if not 'transactions' in data: + return + l = data['transactions'] + + for d in l: + # 这里我们只关心委托成交 + if d['type'] == 'ORDER_FILLED': + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = d['instrument'] + trade.exchange = EXCHANGE_OANDA + trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) + trade.tradeID = str(d['id']) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + trade.orderID = str(d['orderId']) + trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) + trade.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) + trade.offset = OFFSET_NONE + trade.price = d['price'] + trade.volume = d['units'] + trade.tradeTime = getTime(d['time']) + + self.gateway.onTrade(trade) + + self.writeLog(u'成交信息查询完成') + + #---------------------------------------------------------------------- + def onPrice(self, data): + """行情推送""" + if 'tick' not in data: + return + d = data['tick'] + + tick = VtTickData() + tick.gatewayName = self.gatewayName + + tick.symbol = d['instrument'] + tick.exchange = EXCHANGE_OANDA + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + tick.bidPrice1 = d['bid'] + tick.askPrice1 = d['ask'] + tick.time = getTime(d['time']) + + # 做市商的TICK数据只有买卖的报价,因此最新价格选用中间价代替 + tick.lastPrice = (tick.bidPrice1 + tick.askPrice1)/2 + + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def onEvent(self, data): + """事件推送(成交等)""" + if 'transaction' not in data: + return + + d = data['transaction'] + + # 委托成交 + if d['type'] == 'ORDER_FILLED': + # 推送成交事件 + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = d['instrument'] + trade.exchange = EXCHANGE_OANDA + trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) + + trade.tradeID = str(d['id']) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.orderID = str(d['orderId']) + trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) + + trade.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) + trade.offset = OFFSET_NONE + + trade.price = d['price'] + trade.volume = d['units'] + trade.tradeTime = getTime(d['time']) + + self.gateway.onTrade(trade) + + # 推送委托事件 + order = self.orderDict.get(str(d['orderId']), None) + if not order: + return + order.status = STATUS_ALLTRADED + self.gateway.onOrder(order) + + # 委托下达 + elif d['type'] in ['MARKET_ORDER_CREATE', 'LIMIT_ORDER_CREATE']: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['instrument'] + order.exchange = EXCHANGE_OANDA + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID = str(d['id']) + order.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) + order.offset = OFFSET_NONE + order.status = STATUS_NOTTRADED + order.price = d['price'] + order.totalVolume = d['units'] + order.orderTime = getTime(d['time']) + order.vtOrderID = '.'.join([self.gatewayName , order.orderID]) + + self.gateway.onOrder(order) + self.orderDict[order.orderID] = order + + # 委托撤销 + elif d['type'] == 'ORDER_CANCEL': + order = self.orderDict.get(str(d['orderId']), None) + if not order: + return + order.status = STATUS_CANCELLED + self.gateway.onOrder(order) + + #---------------------------------------------------------------------- + def writeLog(self, logContent): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = logContent + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def qryInstruments(self): + """查询合约""" + params = {'accountId': self.accountId} + self.getInstruments(params) + + #---------------------------------------------------------------------- + def qryOrders(self): + """查询委托""" + self.getOrders({}) + + #---------------------------------------------------------------------- + def qryTrades(self): + """查询成交""" + # 最多查询100条记录 + self.getTransactions({'count': 100}) + + #---------------------------------------------------------------------- + def sendOrder_(self, orderReq): + """发送委托""" + params = {} + params['instrument'] = orderReq.symbol + params['units'] = orderReq.volume + params['side'] = directionMap.get(orderReq.direction, '') + params['price'] = orderReq.price + params['type'] = priceTypeMap.get(orderReq.priceType, '') + + # 委托有效期24小时 + expire = datetime.datetime.now() + datetime.timedelta(days=1) + params['expiry'] = expire.isoformat('T') + 'Z' + + self.sendOrder(params) + + #---------------------------------------------------------------------- + def cancelOrder_(self, cancelOrderReq): + """撤销委托""" + self.cancelOrder(cancelOrderReq.orderID) + + +#---------------------------------------------------------------------- +def getTime(t): + """把OANDA返回的时间格式转化为简单的时间字符串""" + return t[11:19] \ No newline at end of file diff --git a/vn.trader/oandaGateway/vnoanda.py b/vn.trader/oandaGateway/vnoanda.py new file mode 100644 index 00000000..570d3f73 --- /dev/null +++ b/vn.trader/oandaGateway/vnoanda.py @@ -0,0 +1,608 @@ +# 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(str(e), reqID) + else: + self.onError(error, reqID) + 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): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getPrices(self, params): + """查询价格""" + return self.sendRequest(FUNCTIONCODE_GETPRICES, params, self.onGetPrices) + + #---------------------------------------------------------------------- + def onGetPrices(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getPriceHisory(self, params): + """查询历史价格数据""" + return self.sendRequest(FUNCTIONCODE_GETPRICEHISTORY, params, self.onGetPriceHistory) + + #---------------------------------------------------------------------- + def onGetPriceHistory(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getAccounts(self): + """查询用户的所有账户""" + return self.sendRequest(FUNCTIONCODE_GETACCOUNTS, {}, self.onGetAccounts) + + #---------------------------------------------------------------------- + def onGetAccounts(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getAccountInfo(self): + """查询账户数据""" + return self.sendRequest(FUNCTIONCODE_GETACCOUNTINFO, {}, self.onGetAccountInfo) + + #---------------------------------------------------------------------- + def onGetAccountInfo(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getOrders(self, params): + """查询所有委托""" + return self.sendRequest(FUNCTIONCODE_GETORDERS, params, self.onGetOrders) + + #---------------------------------------------------------------------- + def onGetOrders(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, params): + """发送委托""" + return self.sendRequest(FUNCTIONCODE_SENDORDER, params, self.onSendOrder) + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getOrderInfo(self, optional): + """查询委托信息""" + return self.sendRequest(FUNCTIONCODE_GETORDERINFO, {}, self.onGetOrderInfo, optional) + + #---------------------------------------------------------------------- + def onGetOrderInfo(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def modifyOrder(self, params, optional): + """修改委托""" + return self.sendRequest(FUNCTIONCODE_MODIFYORDER, params, self.onModifyOrder, optional) + + #---------------------------------------------------------------------- + def onModifyOrder(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def cancelOrder(self, optional): + """查询委托信息""" + return self.sendRequest(FUNCTIONCODE_CANCELORDER, {}, self.onCancelOrder, optional) + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getTrades(self, params): + """查询所有仓位""" + return self.sendRequest(FUNCTIONCODE_GETTRADES, params, self.onGetTrades) + + #---------------------------------------------------------------------- + def onGetTrades(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getTradeInfo(self, optional): + """查询仓位信息""" + return self.sendRequest(FUNCTIONCODE_GETTRADEINFO, {}, self.onGetTradeInfo, optional) + + #---------------------------------------------------------------------- + def onGetTradeInfo(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def modifyTrade(self, params, optional): + """修改仓位""" + return self.sendRequest(FUNCTIONCODE_MODIFYTRADE, params, self.onModifyTrade, optional) + + #---------------------------------------------------------------------- + def onModifyTrade(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def closeTrade(self, optional): + """平仓""" + return self.sendRequest(FUNCTIONCODE_CLOSETRADE, {}, self.onCloseTrade, optional) + + #---------------------------------------------------------------------- + def onCloseTrade(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getPositions(self): + """查询所有汇总仓位""" + return self.sendRequest(FUNCTIONCODE_GETPOSITIONS, {}, self.onGetPositions) + + #---------------------------------------------------------------------- + def onGetPositions(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getPositionInfo(self, optional): + """查询汇总仓位信息""" + return self.sendRequest(FUNCTIONCODE_GETPOSITIONINFO, {}, self.onGetPositionInfo, optional) + + #---------------------------------------------------------------------- + def onGetPositionInfo(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def closePosition(self, optional): + """平仓汇总仓位信息""" + return self.sendRequest(FUNCTIONCODE_CLOSEPOSITION, {}, self.onClosePosition, optional) + + #---------------------------------------------------------------------- + def onClosePosition(self, data, reqID): + """回调函数""" + pass + + + #---------------------------------------------------------------------- + def getTransactions(self, params): + """查询所有资金变动""" + return self.sendRequest(FUNCTIONCODE_GETTRANSACTIONS, params, self.onGetTransactions) + + #---------------------------------------------------------------------- + def onGetTransactions(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getTransactionInfo(self, optional): + """查询资金变动信息""" + return self.sendRequest(FUNCTIONCODE_GETTRANSACTIONINFO, {}, self.onGetTransactionInfo, optional) + + #---------------------------------------------------------------------- + def onGetTransactionInfo(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getAccountHistory(self): + """查询账户资金变动历史""" + return self.sendRequest(FUNCTIONCODE_GETACCOUNTHISTORY, {}, self.onGetAccountHistory) + + #---------------------------------------------------------------------- + def onGetAccountHistory(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getCalendar(self, params): + """查询日历""" + return self.sendRequest(FUNCTIONCODE_GETCALENDAR, params, self.onGetCalendar) + + #---------------------------------------------------------------------- + def onGetCalendar(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getPositionRatios(self, params): + """查询持仓比例""" + return self.sendRequest(FUNCTIONCODE_GETPOSITIONRATIOS, params, self.onGetPositionRatios) + + #---------------------------------------------------------------------- + def onGetPositionRatios(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getSpreads(self, params): + """查询所有仓位""" + return self.sendRequest(FUNCTIONCODE_GETSPREADS, params, self.onGetSpreads) + + #---------------------------------------------------------------------- + def onGetSpreads(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getCommitments(self, params): + """查询交易商持仓情况""" + return self.sendRequest(FUNCTIONCODE_GETCOMMIMENTS, params, self.onGetCommitments) + + #---------------------------------------------------------------------- + def onGetCommitments(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getOrderbook(self, params): + """查询订单簿""" + return self.sendRequest(FUNCTIONCODE_GETORDERBOOK, params, self.onGetOrderbook) + + #---------------------------------------------------------------------- + def onGetOrderbook(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + def getAutochartist(self, params): + """查询Autochartist识别的模式""" + return self.sendRequest(FUNCTIONCODE_GETAUTOCHARTIST, params, self.onGetAutochartist) + + #---------------------------------------------------------------------- + def onGetAutochartist(self, data, reqID): + """回调函数""" + pass + + #---------------------------------------------------------------------- + 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) \ No newline at end of file diff --git a/vn.trader/uiMainWindow.py b/vn.trader/uiMainWindow.py index 1d993410..0b5da758 100644 --- a/vn.trader/uiMainWindow.py +++ b/vn.trader/uiMainWindow.py @@ -91,6 +91,9 @@ class MainWindow(QtGui.QMainWindow): connectIbAction = QtGui.QAction(u'连接IB', self) connectIbAction.triggered.connect(self.connectIb) + connectOandaAction = QtGui.QAction(u'连接OANDA', self) + connectOandaAction.triggered.connect(self.connectOanda) + connectDbAction = QtGui.QAction(u'连接数据库', self) connectDbAction.triggered.connect(self.mainEngine.dbConnect) @@ -118,7 +121,9 @@ class MainWindow(QtGui.QMainWindow): sysMenu.addAction(connectFemasAction) sysMenu.addAction(connectKsotpAction) sysMenu.addAction(connectKsgoldAction) + sysMenu.addSeparator() sysMenu.addAction(connectIbAction) + sysMenu.addAction(connectOandaAction) sysMenu.addSeparator() sysMenu.addAction(connectWindAction) sysMenu.addSeparator() @@ -202,6 +207,11 @@ class MainWindow(QtGui.QMainWindow): """连接Ib""" self.mainEngine.connect('IB') + #---------------------------------------------------------------------- + def connectOanda(self): + """连接OANDA""" + self.mainEngine.connect('OANDA') + #---------------------------------------------------------------------- def test(self): """测试按钮用的函数""" diff --git a/vn.trader/vtConstant.py b/vn.trader/vtConstant.py index 6efc3cdd..e1467b04 100644 --- a/vn.trader/vtConstant.py +++ b/vn.trader/vtConstant.py @@ -66,6 +66,8 @@ EXCHANGE_SMART = 'SMART' # IB智能路由(股票、期权) EXCHANGE_GLOBEX = 'GLOBEX' # CME电子交易平台 EXCHANGE_IDEALPRO = 'IDEALPRO' # IB外汇ECN +EXCHANGE_OANDA = 'OANDA' # OANDA外汇做市商 + # 货币类型 CURRENCY_USD = 'USD' # 美元 CURRENCY_CNY = 'CNY' # 人民币 diff --git a/vn.trader/vtEngine.py b/vn.trader/vtEngine.py index fe5f5ff3..22e857bf 100644 --- a/vn.trader/vtEngine.py +++ b/vn.trader/vtEngine.py @@ -87,6 +87,13 @@ class MainEngine(object): self.addGateway(IbGateway, 'IB') except Exception, e: print e + + try: + from oandaGateway.oandaGateway import OandaGateway + self.addGateway(OandaGateway, 'OANDA') + self.gatewayDict['OANDA'].setQryEnabled(True) + except Exception, e: + print e #---------------------------------------------------------------------- def addGateway(self, gateway, gatewayName=None):