diff --git a/vn.okcoin/test.py b/vn.okcoin/test.py index 6f1bc39a..41863684 100644 --- a/vn.okcoin/test.py +++ b/vn.okcoin/test.py @@ -15,9 +15,9 @@ api.connect(OKCOIN_USD, apiKey, secretKey, True) sleep(1) # 测试现货行情API -api.subscribeSpotTicker(SYMBOL_BTC) +#api.subscribeSpotTicker(SYMBOL_BTC) #api.subscribeSpotTradeData(SYMBOL_BTC) -#api.subscribeSpotDepth(SYMBOL_BTC, DEPTH_20) +api.subscribeSpotDepth(SYMBOL_BTC, DEPTH_20) #api.subscribeSpotKline(SYMBOL_BTC, INTERVAL_1M) # 测试现货交易API diff --git a/vn.okcoin/vnokcoin.py b/vn.okcoin/vnokcoin.py index 2f30a766..b5da6789 100644 --- a/vn.okcoin/vnokcoin.py +++ b/vn.okcoin/vnokcoin.py @@ -41,7 +41,7 @@ INTERVAL_1W = 'week' # 交易代码,需要后缀货币名才能完整 TRADING_SYMBOL_BTC = 'btc_' -TRADING_SYMBOL_LTS = 'ltc_' +TRADING_SYMBOL_LTC = 'ltc_' # 委托类型 TYPE_BUY = 'buy' @@ -86,6 +86,7 @@ class OkCoinApi(object): self.apiKey = '' # 用户名 self.secretKey = '' # 密码 self.host = '' # 服务器地址 + self.currency = '' # 货币类型(usd或者cny) self.ws = None # websocket应用对象 @@ -164,11 +165,7 @@ class OkCoinApi(object): self.thread = Thread(target=self.ws.run_forever) self.thread.start() - - ####################### - ## 现货相关 - ####################### - + #---------------------------------------------------------------------- def sendMarketDataRequest(self, channel): """发送行情请求""" @@ -198,8 +195,12 @@ class OkCoinApi(object): # 使用json打包并发送 j = json.dumps(d) - self.ws.send(j) - + self.ws.send(j) + + ####################### + ## 现货相关 + ####################### + #---------------------------------------------------------------------- def subscribeSpotTicker(self, symbol): """订阅现货普通报价""" @@ -224,8 +225,8 @@ class OkCoinApi(object): def spotTrade(self, symbol, type_, price, amount): """现货委托""" params = {} - params['symbol'] = str(symbol) - params['type'] = str(type_+self.currency) + params['symbol'] = str(symbol+self.currency) + params['type'] = str(type_) params['price'] = str(price) params['amount'] = str(amount) @@ -258,7 +259,7 @@ class OkCoinApi(object): params['symbol'] = str(symbol+self.currency) params['order_id'] = str(orderid) - channel = 'ok_spot%s_orderinfo' + channel = 'ok_spot%s_orderinfo' %(self.currency) self.sendTradingRequest(channel, params) diff --git a/vn.trader/ctaAlgo/ctaBacktesting.py b/vn.trader/ctaAlgo/ctaBacktesting.py index 684024db..50a8027b 100644 --- a/vn.trader/ctaAlgo/ctaBacktesting.py +++ b/vn.trader/ctaAlgo/ctaBacktesting.py @@ -4,6 +4,7 @@ 本文件中包含的是CTA模块的回测引擎,回测引擎的API和CTA引擎一致, 可以使用和实盘相同的代码进行回测。 ''' +from __future__ import division from datetime import datetime, timedelta from collections import OrderedDict @@ -504,6 +505,11 @@ class BacktestingEngine(object): capitalList = [] # 盈亏汇总的时间序列 drawdownList = [] # 回撤的时间序列 + winningResult = 0 # 盈利次数 + losingResult = 0 # 亏损次数 + totalWinning = 0 # 总盈利金额 + totalLosing = 0 # 总亏损金额 + for time, result in resultDict.items(): capital += result.pnl maxCapital = max(capital, maxCapital) @@ -519,6 +525,19 @@ class BacktestingEngine(object): totalCommission += result.commission totalSlippage += result.slippage + if result.pnl >= 0: + winningResult += 1 + totalWinning += result.pnl + else: + losingResult += 1 + totalLosing += result.pnl + + # 计算盈亏相关数据 + winningRate = winningResult/totalResult*100 # 胜率 + averageWinning = totalWinning/winningResult # 平均每笔盈利 + averageLosing = totalLosing/losingResult # 平均每笔亏损 + profitLossRatio = -averageWinning/averageLosing # 盈亏比 + # 返回回测结果 d = {} d['capital'] = capital @@ -531,7 +550,12 @@ class BacktestingEngine(object): d['timeList'] = timeList d['pnlList'] = pnlList d['capitalList'] = capitalList - d['drawdownList'] = drawdownList + d['drawdownList'] = drawdownList + d['winningRate'] = winningRate + d['averageWinning'] = averageWinning + d['averageLosing'] = averageLosing + d['profitLossRatio'] = profitLossRatio + return d #---------------------------------------------------------------------- @@ -551,6 +575,11 @@ class BacktestingEngine(object): self.output(u'平均每笔盈利:\t%s' %formatNumber(d['capital']/d['totalResult'])) self.output(u'平均每笔滑点:\t%s' %formatNumber(d['totalSlippage']/d['totalResult'])) self.output(u'平均每笔佣金:\t%s' %formatNumber(d['totalCommission']/d['totalResult'])) + + self.output(u'胜率\t\t%s%%' %formatNumber(d['winningRate'])) + self.output(u'平均每笔盈利\t%s' %formatNumber(d['averageWinning'])) + self.output(u'平均每笔亏损\t%s' %formatNumber(d['averageLosing'])) + self.output(u'盈亏比:\t%s' %formatNumber(d['profitLossRatio'])) # 绘图 import matplotlib.pyplot as plt @@ -651,11 +680,11 @@ class TradingResult(object): self.exit = exit # 平仓价格 self.volume = volume # 交易数量(+/-代表方向) - self.turnover = (self.entry+self.exit)*size # 成交金额 - self.commission = self.turnover*rate # 手续费成本 - self.slippage = slippage*2*size # 滑点成本 + self.turnover = (self.entry+self.exit)*size*abs(volume) # 成交金额 + self.commission = self.turnover*rate # 手续费成本 + self.slippage = slippage*2*size*abs(volume) # 滑点成本 self.pnl = ((self.exit - self.entry) * volume * size - - self.commission - self.slippage) # 净盈亏 + - self.commission - self.slippage) # 净盈亏 ######################################################################## diff --git a/vn.trader/ctaAlgo/strategyAtrRsi.py b/vn.trader/ctaAlgo/strategyAtrRsi.py index b8374c2e..4832d8fe 100644 --- a/vn.trader/ctaAlgo/strategyAtrRsi.py +++ b/vn.trader/ctaAlgo/strategyAtrRsi.py @@ -259,21 +259,21 @@ if __name__ == '__main__': # 设置使用的历史数据库 engine.setDatabase(MINUTE_DB_NAME, 'IF0000') - ## 在引擎中创建策略对象 - #d = {'atrLength': 11} - #engine.initStrategy(AtrRsiStrategy, d) + # 在引擎中创建策略对象 + d = {'atrLength': 11} + engine.initStrategy(AtrRsiStrategy, d) - ## 开始跑回测 - #engine.runBacktesting() + # 开始跑回测 + engine.runBacktesting() - ## 显示回测结果 - #engine.showBacktestingResult() + # 显示回测结果 + engine.showBacktestingResult() - # 跑优化 - setting = OptimizationSetting() # 新建一个优化任务设置对象 - setting.setOptimizeTarget('capital') # 设置优化排序的目标是策略净盈利 - setting.addParameter('atrLength', 11, 12, 1) # 增加第一个优化参数atrLength,起始11,结束12,步进1 - setting.addParameter('atrMa', 20, 30, 5) # 增加第二个优化参数atrMa,起始20,结束30,步进1 - engine.runOptimization(AtrRsiStrategy, setting) # 运行优化函数,自动输出结果 + ## 跑优化 + #setting = OptimizationSetting() # 新建一个优化任务设置对象 + #setting.setOptimizeTarget('capital') # 设置优化排序的目标是策略净盈利 + #setting.addParameter('atrLength', 11, 12, 1) # 增加第一个优化参数atrLength,起始11,结束12,步进1 + #setting.addParameter('atrMa', 20, 30, 5) # 增加第二个优化参数atrMa,起始20,结束30,步进1 + #engine.runOptimization(AtrRsiStrategy, setting) # 运行优化函数,自动输出结果 \ No newline at end of file diff --git a/vn.trader/okcoinGateway/OKCOIN_connect.json b/vn.trader/okcoinGateway/OKCOIN_connect.json new file mode 100644 index 00000000..8240e55e --- /dev/null +++ b/vn.trader/okcoinGateway/OKCOIN_connect.json @@ -0,0 +1,7 @@ +{ + "host": "CNY", + "apiKey": "OKCOIN网站申请", + "secretKey": "OKCOIN网站申请", + "trace": false, + "leverage": 20 +} \ No newline at end of file diff --git a/vn.trader/okcoinGateway/__init__.py b/vn.trader/okcoinGateway/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vn.trader/okcoinGateway/okcoinGateway.py b/vn.trader/okcoinGateway/okcoinGateway.py new file mode 100644 index 00000000..ee1d86aa --- /dev/null +++ b/vn.trader/okcoinGateway/okcoinGateway.py @@ -0,0 +1,657 @@ +# encoding: UTF-8 + +''' +vn.okcoin的gateway接入 + +注意: +1. 该接口尚处于测试阶段,用于实盘请谨慎 +2. 目前仅支持USD和CNY的现货交易,USD的期货合约交易暂不支持 +''' + + +import os +import json +from datetime import datetime +from copy import copy +from threading import Condition + +import vnokcoin +from vtGateway import * + +# 价格类型映射 +priceTypeMap = {} +priceTypeMap['buy'] = (DIRECTION_LONG, PRICETYPE_LIMITPRICE) +priceTypeMap['buy_market'] = (DIRECTION_LONG, PRICETYPE_MARKETPRICE) +priceTypeMap['sell'] = (DIRECTION_SHORT, PRICETYPE_LIMITPRICE) +priceTypeMap['sell_market'] = (DIRECTION_SHORT, PRICETYPE_MARKETPRICE) +priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} + +# 方向类型映射 +directionMap = {} +directionMapReverse = {v: k for k, v in directionMap.items()} + +# 委托状态印射 +statusMap = {} +statusMap[-1] = STATUS_CANCELLED +statusMap[0] = STATUS_NOTTRADED +statusMap[1] = STATUS_PARTTRADED +statusMap[2] = STATUS_ALLTRADED +statusMap[4] = STATUS_UNKNOWN + +############################################ +## 交易合约代码 +############################################ + +# USD +BTC_USD_SPOT = 'BTC_USD_SPOT' +BTC_USD_THISWEEK = 'BTC_USD_THISWEEK' +BTC_USD_NEXTWEEK = 'BTC_USD_NEXTWEEK' +BTC_USD_QUARTER = 'BTC_USD_QUARTER' + +LTC_USD_SPOT = 'LTC_USD_SPOT' +LTC_USD_THISWEEK = 'LTC_USD_THISWEEK' +LTC_USD_NEXTWEEK = 'LTC_USD_NEXTWEEK' +LTC_USD_QUARTER = 'LTC_USD_QUARTER' + +# CNY +BTC_CNY_SPOT = 'BTC_CNY_SPOT' +LTC_CNY_SPOT = 'LTC_CNY_SPOT' + +# 印射字典 +spotSymbolMap = {} +spotSymbolMap['ltc_usd'] = LTC_USD_SPOT +spotSymbolMap['btc_usd'] = BTC_USD_SPOT +spotSymbolMap['ltc_cny'] = LTC_CNY_SPOT +spotSymbolMap['btc_cny'] = BTC_CNY_SPOT +spotSymbolMapReverse = {v: k for k, v in spotSymbolMap.items()} + + +############################################ +## Channel和Symbol的印射 +############################################ +channelSymbolMap = {} + +# USD +channelSymbolMap['ok_sub_spotusd_btc_ticker'] = BTC_USD_SPOT +channelSymbolMap['ok_sub_spotusd_ltc_ticker'] = LTC_USD_SPOT + +channelSymbolMap['ok_sub_spotusd_btc_depth_20'] = BTC_USD_SPOT +channelSymbolMap['ok_sub_spotusd_ltc_depth_20'] = LTC_USD_SPOT + +# CNY +channelSymbolMap['ok_sub_spotcny_btc_ticker'] = BTC_CNY_SPOT +channelSymbolMap['ok_sub_spotcny_ltc_ticker'] = LTC_CNY_SPOT + +channelSymbolMap['ok_sub_spotcny_btc_depth_20'] = BTC_CNY_SPOT +channelSymbolMap['ok_sub_spotcny_ltc_depth_20'] = LTC_CNY_SPOT + + + + +######################################################################## +class OkcoinGateway(VtGateway): + """OkCoin接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName='OKCOIN'): + """Constructor""" + super(OkcoinGateway, self).__init__(eventEngine, gatewayName) + + self.api = Api(self) + + self.leverage = 0 + self.connected = False + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + # 载入json文件 + fileName = self.gatewayName + '_connect.json' + fileName = os.getcwd() + '/okcoinGateway/' + 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: + host = str(setting['host']) + apiKey = str(setting['apiKey']) + secretKey = str(setting['secretKey']) + trace = setting['trace'] + leverage = setting['leverage'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 初始化接口 + self.leverage = leverage + + if host == 'CNY': + host = vnokcoin.OKCOIN_CNY + else: + host = vnokcoin.OKCOIN_USD + + self.api.connect(host, apiKey, secretKey, trace) + + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'接口初始化成功' + self.onLog(log) + + # 启动查询 + self.initQuery() + self.startQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.api.spotSendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.api.spotCancel(cancelOrderReq) + + #---------------------------------------------------------------------- + def qryAccount(self): + """查询账户资金""" + self.api.spotUserInfo() + + #---------------------------------------------------------------------- + def qryPosition(self): + """查询持仓""" + pass + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + pass + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.qryAccount] + + 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(vnokcoin.OkCoinApi): + """OkCoin的API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(Api, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.cbDict = {} + self.tickDict = {} + self.orderDict = {} + + self.lastOrderID = '' + self.orderCondition = Condition() + + self.initCallback() + + #---------------------------------------------------------------------- + def onMessage(self, ws, evt): + """信息推送""" + data = self.readData(evt)[0] + channel = data['channel'] + callback = self.cbDict[channel] + callback(data) + + #---------------------------------------------------------------------- + def onError(self, ws, evt): + """错误推送""" + error = VtErrorData() + error.gatewayName = self.gatewayName + error.errorMsg = str(evt) + self.gateway.onError(error) + + #---------------------------------------------------------------------- + def onClose(self, ws): + """接口断开""" + self.gateway.connected = True + self.writeLog(u'服务器连接断开') + + #---------------------------------------------------------------------- + def onOpen(self, ws): + self.gateway.connected = True + self.writeLog(u'服务器连接成功') + + # 连接后查询账户和委托数据 + self.spotUserInfo() + + self.spotOrderInfo(vnokcoin.TRADING_SYMBOL_LTC, '-1') + self.spotOrderInfo(vnokcoin.TRADING_SYMBOL_BTC, '-1') + + # 连接后订阅现货的成交和账户数据 + self.subscribeSpotTrades() + self.subscribeSpotUserInfo() + + self.subscribeSpotTicker(vnokcoin.SYMBOL_BTC) + self.subscribeSpotTicker(vnokcoin.SYMBOL_LTC) + + self.subscribeSpotDepth(vnokcoin.SYMBOL_BTC, vnokcoin.DEPTH_20) + self.subscribeSpotDepth(vnokcoin.SYMBOL_LTC, vnokcoin.DEPTH_20) + + # 如果连接的是USD网站则订阅期货相关回报数据 + if self.currency == vnokcoin.CURRENCY_USD: + self.subscribeFutureTrades() + self.subscribeFutureUserInfo() + self.subscribeFuturePositions() + + # 返回合约信息 + if self.currency == vnokcoin.CURRENCY_CNY: + l = self.generateCnyContract() + else: + l = self.generateUsdContract() + + for contract in l: + contract.gatewayName = self.gatewayName + self.gateway.onContract(contract) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """快速记录日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def initCallback(self): + """初始化回调函数""" + # USD_SPOT + self.cbDict['ok_sub_spotusd_btc_ticker'] = self.onTicker + self.cbDict['ok_sub_spotusd_ltc_ticker'] = self.onTicker + + self.cbDict['ok_sub_spotusd_btc_depth_20'] = self.onDepth + self.cbDict['ok_sub_spotusd_ltc_depth_20'] = self.onDepth + + self.cbDict['ok_spotusd_userinfo'] = self.onSpotUserInfo + self.cbDict['ok_spotusd_orderinfo'] = self.onSpotOrderInfo + + self.cbDict['ok_sub_spotusd_userinfo'] = self.onSpotSubUserInfo + self.cbDict['ok_sub_spotusd_trades'] = self.onSpotSubTrades + + self.cbDict['ok_spotusd_trade'] = self.onSpotTrade + self.cbDict['ok_spotusd_cancel_order'] = self.onSpotCancelOrder + + # CNY_SPOT + self.cbDict['ok_sub_spotcny_btc_ticker'] = self.onTicker + self.cbDict['ok_sub_spotcny_ltc_ticker'] = self.onTicker + + self.cbDict['ok_sub_spotcny_btc_depth_20'] = self.onDepth + self.cbDict['ok_sub_spotcny_ltc_depth_20'] = self.onDepth + + self.cbDict['ok_spotcny_userinfo'] = self.onSpotUserInfo + self.cbDict['ok_spotcny_orderinfo'] = self.onSpotOrderInfo + + self.cbDict['ok_sub_spotcny_userinfo'] = self.onSpotSubUserInfo + self.cbDict['ok_sub_spotcny_trades'] = self.onSpotSubTrades + + self.cbDict['ok_spotcny_trade'] = self.onSpotTrade + self.cbDict['ok_spotcny_cancel_order'] = self.onSpotCancelOrder + + # USD_FUTURES + + #---------------------------------------------------------------------- + def onTicker(self, data): + """""" + if 'data' not in data: + return + + channel = data['channel'] + symbol = channelSymbolMap[channel] + + if symbol not in self.tickDict: + tick = VtTickData() + tick.symbol = symbol + tick.vtSymbol = symbol + tick.gatewayName = self.gatewayName + self.tickDict[symbol] = tick + else: + tick = self.tickDict[symbol] + + rawData = data['data'] + tick.highPrice = float(rawData['high']) + tick.lowPrice = float(rawData['low']) + tick.lastPrice = float(rawData['last']) + tick.volume = float(rawData['vol'].replace(',', '')) + tick.date, tick.time = generateDateTime(rawData['timestamp']) + + newtick = copy(tick) + self.gateway.onTick(newtick) + + #---------------------------------------------------------------------- + def onDepth(self, data): + """""" + if 'data' not in data: + return + + channel = data['channel'] + symbol = channelSymbolMap[channel] + + if symbol not in self.tickDict: + tick = VtTickData() + tick.symbol = symbol + tick.vtSymbol = symbol + tick.gatewayName = self.gatewayName + self.tickDict[symbol] = tick + else: + tick = self.tickDict[symbol] + + if 'data' not in data: + return + rawData = data['data'] + + tick.bidPrice1, tick.bidVolume1 = rawData['bids'][0] + tick.bidPrice2, tick.bidVolume2 = rawData['bids'][1] + tick.bidPrice3, tick.bidVolume3 = rawData['bids'][2] + tick.bidPrice4, tick.bidVolume4 = rawData['bids'][3] + tick.bidPrice5, tick.bidVolume5 = rawData['bids'][4] + + tick.askPrice1, tick.askVolume1 = rawData['asks'][0] + tick.askPrice2, tick.askVolume2 = rawData['asks'][1] + tick.askPrice3, tick.askVolume3 = rawData['asks'][2] + tick.askPrice4, tick.askVolume4 = rawData['asks'][3] + tick.askPrice5, tick.askVolume5 = rawData['asks'][4] + + newtick = copy(tick) + self.gateway.onTick(newtick) + + #---------------------------------------------------------------------- + def onSpotUserInfo(self, data): + """现货账户资金推送""" + rawData = data['data'] + info = rawData['info'] + funds = rawData['info']['funds'] + + # 持仓信息 + for symbol in ['btc', 'ltc', self.currency]: + if symbol in funds['free']: + pos = VtPositionData() + pos.gatewayName = self.gatewayName + + pos.symbol = symbol + pos.vtSymbol = symbol + pos.vtPositionName = symbol + pos.direction = DIRECTION_NET + + pos.frozen = float(funds['freezed'][symbol]) + pos.position = pos.frozen + float(funds['free'][symbol]) + + self.gateway.onPosition(pos) + + # 账户资金 + account = VtAccountData() + account.gatewayName = self.gatewayName + account.accountID = self.gatewayName + account.vtAccountID = account.accountID + account.balance = float(funds['asset']['net']) + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onSpotSubUserInfo(self, data): + """现货账户资金推送""" + if 'data' not in data: + return + + rawData = data['data'] + info = rawData['info'] + + # 持仓信息 + for symbol in ['btc', 'ltc', self.currency]: + if symbol in info['free']: + pos = VtPositionData() + pos.gatewayName = self.gatewayName + + pos.symbol = symbol + pos.vtSymbol = symbol + pos.vtPositionName = symbol + pos.direction = DIRECTION_NET + + pos.frozen = float(info['freezed'][symbol]) + pos.position = pos.frozen + float(info['free'][symbol]) + + self.gateway.onPosition(pos) + + #---------------------------------------------------------------------- + def onSpotSubTrades(self, data): + """成交和委托推送""" + if 'data' not in data: + return + rawData = data['data'] + + # 委托信息 + orderID = str(rawData['orderId']) + if orderID not in self.orderDict: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = spotSymbolMap[rawData['symbol']] + order.vtSymbol = order.symbol + + order.orderID = str(rawData['orderId']) + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.price = float(rawData['tradeUnitPrice']) + order.totalVolume = float(rawData['tradeAmount']) + order.direction, priceType = priceTypeMap[rawData['tradeType']] + + self.orderDict[orderID] = order + else: + order = self.orderDict[orderID] + + order.tradedVolume = float(rawData['completedTradeAmount']) + order.status = statusMap[rawData['status']] + + self.gateway.onOrder(copy(order)) + + # 成交信息 + if 'sigTradeAmount' in rawData and float(rawData['sigTradeAmount'])>0: + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = spotSymbolMap[rawData['symbol']] + trade.vtSymbol = order.symbol + + trade.tradeID = str(rawData['id']) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.orderID = str(rawData['orderId']) + trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) + + trade.price = float(rawData['sigTradePrice']) + trade.volume = float(rawData['sigTradeAmount']) + + trade.direction, priceType = priceTypeMap[rawData['tradeType']] + + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onSpotOrderInfo(self, data): + """委托信息查询回调""" + rawData = data['data'] + + for d in rawData['orders']: + orderID = str(d['order_id']) + + if orderID not in self.orderDict: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = spotSymbolMap[d['symbol']] + order.vtSymbol = order.symbol + + order.orderID = str(d['order_id']) + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.price = d['price'] + order.totalVolume = d['amount'] + order.direction, priceType = priceTypeMap[d['type']] + + self.orderDict[orderID] = order + else: + order = self.orderDict[orderID] + + order.tradedVolume = d['deal_amount'] + order.status = statusMap[d['status']] + + self.gateway.onOrder(copy(order)) + + #---------------------------------------------------------------------- + def generateSpecificContract(self, contract, symbol): + """生成合约""" + new = copy(contract) + new.symbol = symbol + new.vtSymbol = symbol + new.name = symbol + return new + + #---------------------------------------------------------------------- + def generateCnyContract(self): + """生成CNY合约信息""" + contractList = [] + + contract = VtContractData() + contract.exchange = EXCHANGE_OKCOIN + contract.productClass = PRODUCT_SPOT + contract.size = 1 + contract.priceTick = 0.01 + + contractList.append(self.generateSpecificContract(contract, BTC_CNY_SPOT)) + contractList.append(self.generateSpecificContract(contract, LTC_CNY_SPOT)) + + return contractList + + #---------------------------------------------------------------------- + def generateUsdContract(self): + """生成USD合约信息""" + contractList = [] + + # 现货 + contract = VtContractData() + contract.exchange = EXCHANGE_OKCOIN + contract.productClass = PRODUCT_SPOT + contract.size = 1 + contract.priceTick = 0.01 + + contractList.append(self.generateSpecificContract(contract, BTC_USD_SPOT)) + contractList.append(self.generateSpecificContract(contract, LTC_USD_SPOT)) + + # 期货 + contract.productClass = PRODUCT_FUTURES + + contractList.append(self.generateSpecificContract(contract, BTC_USD_THISWEEK)) + contractList.append(self.generateSpecificContract(contract, BTC_USD_NEXTWEEK)) + contractList.append(self.generateSpecificContract(contract, BTC_USD_QUARTER)) + contractList.append(self.generateSpecificContract(contract, LTC_USD_THISWEEK)) + contractList.append(self.generateSpecificContract(contract, LTC_USD_NEXTWEEK)) + contractList.append(self.generateSpecificContract(contract, LTC_USD_QUARTER)) + + return contractList + + #---------------------------------------------------------------------- + def onSpotTrade(self, data): + """委托回报""" + rawData = data['data'] + self.lastOrderID = rawData['order_id'] + + # 收到委托号后,通知发送委托的线程返回委托号 + self.orderCondition.acquire() + self.orderCondition.notify() + self.orderCondition.release() + + #---------------------------------------------------------------------- + def onSpotCancelOrder(self, data): + """撤单回报""" + pass + + #---------------------------------------------------------------------- + def spotSendOrder(self, req): + """发单""" + symbol = spotSymbolMapReverse[req.symbol][:4] + type_ = priceTypeMapReverse[(req.direction, req.priceType)] + self.spotTrade(symbol, type_, str(req.price), str(req.volume)) + + # 等待发单回调推送委托号信息 + self.orderCondition.acquire() + self.orderCondition.wait() + self.orderCondition.release() + + vtOrderID = '.'.join([self.gatewayName, self.lastOrderID]) + self.lastOrderID = '' + return vtOrderID + + #---------------------------------------------------------------------- + def spotCancel(self, req): + """撤单""" + symbol = spotSymbolMapReverse[req.symbol][:4] + self.spotCancelOrder(symbol, req.orderID) + + +#---------------------------------------------------------------------- +def generateDateTime(s): + """生成时间""" + dt = datetime.fromtimestamp(float(s)/1e3) + time = dt.strftime("%H:%M:%S.%f") + date = dt.strftime("%Y%m%d") + return date, time \ No newline at end of file diff --git a/vn.trader/okcoinGateway/vnokcoin.py b/vn.trader/okcoinGateway/vnokcoin.py new file mode 100644 index 00000000..b5da6789 --- /dev/null +++ b/vn.trader/okcoinGateway/vnokcoin.py @@ -0,0 +1,382 @@ +# encoding: UTF-8 + +import hashlib +import zlib +import json +from time import sleep +from threading import Thread + +import websocket + + +# OKCOIN网站 +OKCOIN_CNY = 'wss://real.okcoin.cn:10440/websocket/okcoinapi' +OKCOIN_USD = 'wss://real.okcoin.com:10440/websocket/okcoinapi' + +# 账户货币代码 +CURRENCY_CNY = 'cny' +CURRENCY_USD = 'usd' + +# 电子货币代码 +SYMBOL_BTC = 'btc' +SYMBOL_LTC = 'ltc' + +# 行情深度 +DEPTH_20 = 20 +DEPTH_60 = 60 + +# K线时间区间 +INTERVAL_1M = '1min' +INTERVAL_3M = '3min' +INTERVAL_5M = '5min' +INTERVAL_15M = '15min' +INTERVAL_30M = '30min' +INTERVAL_1H = '1hour' +INTERVAL_2H = '2hour' +INTERVAL_4H = '4hour' +INTERVAL_6H = '6hour' +INTERVAL_1D = 'day' +INTERVAL_3D = '3day' +INTERVAL_1W = 'week' + +# 交易代码,需要后缀货币名才能完整 +TRADING_SYMBOL_BTC = 'btc_' +TRADING_SYMBOL_LTC = 'ltc_' + +# 委托类型 +TYPE_BUY = 'buy' +TYPE_SELL = 'sell' +TYPE_BUY_MARKET = 'buy_market' +TYPE_SELL_MARKET = 'sell_market' + +# 期货合约到期类型 +FUTURE_EXPIRY_THIS_WEEK = 'this_week' +FUTURE_EXPIRY_NEXT_WEEK = 'next_week' +FUTURE_EXPIRY_QUARTER = 'quarter' + +# 期货委托类型 +FUTURE_TYPE_LONG = 1 +FUTURE_TYPE_SHORT = 2 +FUTURE_TYPE_SELL = 3 +FUTURE_TYPE_COVER = 4 + +# 期货是否用现价 +FUTURE_ORDER_MARKET = 1 +FUTURE_ORDER_LIMIT = 0 + +# 期货杠杆 +FUTURE_LEVERAGE_10 = 10 +FUTURE_LEVERAGE_20 = 20 + +# 委托状态 +ORDER_STATUS_NOTTRADED = 0 +ORDER_STATUS_PARTTRADED = 1 +ORDER_STATUS_ALLTRADED = 2 +ORDER_STATUS_CANCELLED = -1 +ORDER_STATUS_CANCELLING = 4 + + +######################################################################## +class OkCoinApi(object): + """基于Websocket的API对象""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' # 用户名 + self.secretKey = '' # 密码 + self.host = '' # 服务器地址 + + self.currency = '' # 货币类型(usd或者cny) + + self.ws = None # websocket应用对象 + self.thread = None # 工作线程 + + ####################### + ## 通用函数 + ####################### + + #---------------------------------------------------------------------- + def readData(self, evt): + """解压缩推送收到的数据""" + # 创建解压器 + decompress = zlib.decompressobj(-zlib.MAX_WBITS) + + # 将原始数据解压成字符串 + inflated = decompress.decompress(evt) + decompress.flush() + + # 通过json解析字符串 + data = json.loads(inflated) + + return data + + #---------------------------------------------------------------------- + def generateSign(self, params): + """生成签名""" + l = [] + for key in sorted(params.keys()): + l.append('%s=%s' %(key, params[key])) + l.append('secret_key=%s' %self.secretKey) + sign = '&'.join(l) + return hashlib.md5(sign.encode('utf-8')).hexdigest().upper() + + #---------------------------------------------------------------------- + def onMessage(self, ws, evt): + """信息推送""" + print 'onMessage' + data = self.readData(evt) + print data + + #---------------------------------------------------------------------- + def onError(self, ws, evt): + """错误推送""" + print 'onError' + print evt + + #---------------------------------------------------------------------- + def onClose(self, ws): + """接口断开""" + print 'onClose' + + #---------------------------------------------------------------------- + def onOpen(self, ws): + """接口打开""" + print 'onOpen' + + #---------------------------------------------------------------------- + def connect(self, host, apiKey, secretKey, trace=False): + """连接服务器""" + self.host = host + self.apiKey = apiKey + self.secretKey = secretKey + + if self.host == OKCOIN_CNY: + self.currency = CURRENCY_CNY + else: + self.currency = CURRENCY_USD + + websocket.enableTrace(trace) + + self.ws = websocket.WebSocketApp(host, + on_message=self.onMessage, + on_error=self.onError, + on_close=self.onClose, + on_open=self.onOpen) + + self.thread = Thread(target=self.ws.run_forever) + self.thread.start() + + #---------------------------------------------------------------------- + def sendMarketDataRequest(self, channel): + """发送行情请求""" + # 生成请求 + d = {} + d['event'] = 'addChannel' + d['binary'] = True + d['channel'] = channel + + # 使用json打包并发送 + j = json.dumps(d) + self.ws.send(j) + + #---------------------------------------------------------------------- + def sendTradingRequest(self, channel, params): + """发送交易请求""" + # 在参数字典中加上api_key和签名字段 + params['api_key'] = self.apiKey + params['sign'] = self.generateSign(params) + + # 生成请求 + d = {} + d['event'] = 'addChannel' + d['binary'] = True + d['channel'] = channel + d['parameters'] = params + + # 使用json打包并发送 + j = json.dumps(d) + self.ws.send(j) + + ####################### + ## 现货相关 + ####################### + + #---------------------------------------------------------------------- + def subscribeSpotTicker(self, symbol): + """订阅现货普通报价""" + self.sendMarketDataRequest('ok_sub_spot%s_%s_ticker' %(self.currency, symbol)) + + #---------------------------------------------------------------------- + def subscribeSpotDepth(self, symbol, depth): + """订阅现货深度报价""" + self.sendMarketDataRequest('ok_sub_spot%s_%s_depth_%s' %(self.currency, symbol, depth)) + + #---------------------------------------------------------------------- + def subscribeSpotTradeData(self, symbol): + """订阅现货成交记录""" + self.sendMarketDataRequest('ok_sub_spot%s_%s_trades' %(self.currency, symbol)) + + #---------------------------------------------------------------------- + def subscribeSpotKline(self, symbol, interval): + """订阅现货K线""" + self.sendMarketDataRequest('ok_sub_spot%s_%s_kline_%s' %(self.currency, symbol, interval)) + + #---------------------------------------------------------------------- + def spotTrade(self, symbol, type_, price, amount): + """现货委托""" + params = {} + params['symbol'] = str(symbol+self.currency) + params['type'] = str(type_) + params['price'] = str(price) + params['amount'] = str(amount) + + channel = 'ok_spot%s_trade' %(self.currency) + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def spotCancelOrder(self, symbol, orderid): + """现货撤单""" + params = {} + params['symbol'] = str(symbol+self.currency) + params['order_id'] = str(orderid) + + channel = 'ok_spot%s_cancel_order' %(self.currency) + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def spotUserInfo(self): + """查询现货账户""" + channel = 'ok_spot%s_userinfo' %(self.currency) + + self.sendTradingRequest(channel, {}) + + #---------------------------------------------------------------------- + def spotOrderInfo(self, symbol, orderid): + """查询现货委托信息""" + params = {} + params['symbol'] = str(symbol+self.currency) + params['order_id'] = str(orderid) + + channel = 'ok_spot%s_orderinfo' %(self.currency) + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def subscribeSpotTrades(self): + """订阅现货成交信息""" + channel = 'ok_sub_spot%s_trades' %(self.currency) + + self.sendTradingRequest(channel, {}) + + #---------------------------------------------------------------------- + def subscribeSpotUserInfo(self): + """订阅现货账户信息""" + channel = 'ok_sub_spot%s_userinfo' %(self.currency) + + self.sendTradingRequest(channel, {}) + + ####################### + ## 期货相关 + ####################### + + #---------------------------------------------------------------------- + def subscribeFutureTicker(self, symbol, expiry): + """订阅期货普通报价""" + self.sendMarketDataRequest('ok_sub_future%s_%s_ticker_%s' %(self.currency, symbol, expiry)) + + #---------------------------------------------------------------------- + def subscribeFutureDepth(self, symbol, expiry, depth): + """订阅期货深度报价""" + self.sendMarketDataRequest('ok_sub_future%s_%s_depth_%s_%s' %(self.currency, symbol, + expiry, depth)) + + #---------------------------------------------------------------------- + def subscribeFutureTradeData(self, symbol, expiry): + """订阅期货成交记录""" + self.sendMarketDataRequest('ok_sub_future%s_%s_trade_%s' %(self.currency, symbol, expiry)) + + #---------------------------------------------------------------------- + def subscribeFutureKline(self, symbol, expiry, interval): + """订阅期货K线""" + self.sendMarketDataRequest('ok_sub_future%s_%s_kline_%s_%s' %(self.currency, symbol, + expiry, interval)) + + #---------------------------------------------------------------------- + def subscribeFutureIndex(self, symbol): + """订阅期货指数""" + self.sendMarketDataRequest('ok_sub_future%s_%s_index' %(self.currency, symbol)) + + #---------------------------------------------------------------------- + def futureTrade(self, symbol, expiry, type_, price, amount, order, leverage): + """期货委托""" + params = {} + params['symbol'] = str(symbol+self.currency) + params['type'] = str(type_) + params['price'] = str(price) + params['amount'] = str(amount) + params['contract_type'] = str(expiry) + params['match_price'] = str(order) + params['lever_rate'] = str(leverage) + + channel = 'ok_future%s_trade' %(self.currency) + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def futureCancelOrder(self, symbol, expiry, orderid): + """期货撤单""" + params = {} + params['symbol'] = str(symbol+self.currency) + params['order_id'] = str(orderid) + params['contract_type'] = str(expiry) + + channel = 'ok_future%s_cancel_order' %(self.currency) + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def futureUserInfo(self): + """查询期货账户""" + channel = 'ok_future%s_userinfo' %(self.currency) + + self.sendTradingRequest(channel, {}) + + #---------------------------------------------------------------------- + def futureOrderInfo(self, symbol, expiry, orderid, status, page, length): + """查询期货委托信息""" + params = {} + params['symbol'] = str(symbol+self.currency) + params['order_id'] = str(orderid) + params['contract_type'] = expiry + params['status'] = status + params['current_page'] = page + params['page_length'] = length + + channel = 'ok_future%s_orderinfo' + + self.sendTradingRequest(channel, params) + + #---------------------------------------------------------------------- + def subscribeFutureTrades(self): + """订阅期货成交信息""" + channel = 'ok_sub_future%s_trades' %(self.currency) + + self.sendTradingRequest(channel, {}) + + #---------------------------------------------------------------------- + def subscribeFutureUserInfo(self): + """订阅期货账户信息""" + channel = 'ok_sub_future%s_userinfo' %(self.currency) + + self.sendTradingRequest(channel, {}) + + #---------------------------------------------------------------------- + def subscribeFuturePositions(self): + """订阅期货持仓信息""" + channel = 'ok_sub_future%s_positions' %(self.currency) + + self.sendTradingRequest(channel, {}) + + diff --git a/vn.trader/uiMainWindow.py b/vn.trader/uiMainWindow.py index b3a3d32f..6e77699c 100644 --- a/vn.trader/uiMainWindow.py +++ b/vn.trader/uiMainWindow.py @@ -89,6 +89,9 @@ class MainWindow(QtGui.QMainWindow): connectOandaAction = QtGui.QAction(u'连接OANDA', self) connectOandaAction.triggered.connect(self.connectOanda) + connectOkcoinAction = QtGui.QAction(u'连接OKCOIN', self) + connectOkcoinAction.triggered.connect(self.connectOkcoin) + connectDbAction = QtGui.QAction(u'连接数据库', self) connectDbAction.triggered.connect(self.mainEngine.dbConnect) @@ -137,6 +140,8 @@ class MainWindow(QtGui.QMainWindow): sysMenu.addAction(connectIbAction) if 'OANDA' in self.mainEngine.gatewayDict: sysMenu.addAction(connectOandaAction) + if 'OKCOIN' in self.mainEngine.gatewayDict: + sysMenu.addAction(connectOkcoinAction) sysMenu.addSeparator() if 'Wind' in self.mainEngine.gatewayDict: sysMenu.addAction(connectWindAction) @@ -239,6 +244,11 @@ class MainWindow(QtGui.QMainWindow): """连接OANDA""" self.mainEngine.connect('OANDA') + #---------------------------------------------------------------------- + def connectOkcoin(self): + """连接OKCOIN""" + self.mainEngine.connect('OKCOIN') + #---------------------------------------------------------------------- def test(self): """测试按钮用的函数""" diff --git a/vn.trader/vtConstant.py b/vn.trader/vtConstant.py index d8ed868a..380a158c 100644 --- a/vn.trader/vtConstant.py +++ b/vn.trader/vtConstant.py @@ -69,6 +69,7 @@ EXCHANGE_GLOBEX = 'GLOBEX' # CME电子交易平台 EXCHANGE_IDEALPRO = 'IDEALPRO' # IB外汇ECN EXCHANGE_OANDA = 'OANDA' # OANDA外汇做市商 +EXCHANGE_OKCOIN = 'OKCOIN' # OKCOIN比特币交易所 # 货币类型 CURRENCY_USD = 'USD' # 美元 diff --git a/vn.trader/vtEngine.py b/vn.trader/vtEngine.py index 0ce0bd66..583d4fc9 100644 --- a/vn.trader/vtEngine.py +++ b/vn.trader/vtEngine.py @@ -114,6 +114,13 @@ class MainEngine(object): self.gatewayDict['OANDA'].setQryEnabled(True) except Exception, e: print e + + try: + from okcoinGateway.okcoinGateway import OkcoinGateway + self.addGateway(OkcoinGateway, 'OKCOIN') + self.gatewayDict['OKCOIN'].setQryEnabled(True) + except Exception, e: + print e #---------------------------------------------------------------------- def addGateway(self, gateway, gatewayName=None):