IB+python实现程序化交易 - 小众知识

IB+python实现程序化交易

2013年01月27日 14:18:05 苏内容
  标签: IB/python//ibpy
阅读:7328
从上周末开始边学边做程序化交易。目标是每天开盘前做一下设置,然后就不用盯盘了。博客里就不讨论策略了,只讨论技术。我是python初学者,但是选择这个语言有几方面原因:它语言简洁;做数据处理比c++,java方便;比matlab便宜(免费),而且节省空间。另外正好借这个机会希望掌握这门语言。
我用的是mac os。unix的操作可能相似。如果是用windows,我就帮不上忙了,请自行google相应步骤。
以下假设你的机器已经安装了python 2.7.
 
首先是安装ibpy。ibpy是ib的python语言api,非官方的。
google ibpy。去github或者google code下载,注意github的版本更新一些。而网上的一些例子用的是老版本。这个以后我会提到。把下载下来的包放到你想要的文件夹下后,在terminal里 sudo python setup.py install。
 
装好后,我先实验了两个功能,一个是读取股票价格,另一个是发送买单。具体实现下篇博文慢慢介绍。下图是读取股票价格的运行截图。猜猜是什么的价格?
 
IB+python实现程序化交易(1)
最近有点儿忙,只能每周一更了。
建立好python环境后运行tws客户端。注意要去configuration里面把api的功能开启。另外建议把127.0.0.1加为信任ip,免得每次tws都会弹个对话框跟你确认。(本来想截个图方便读者,可是tws这会儿登不了--周六凌晨)
首先是建立链接,
需要引用 connection或者是ibConnection。后者封装了前者,用起来更方便些。
from ib.opt import ibConnection
有了上面的引用后,下面两行就链接到了tws:
con = ibConnection()
con.connect()
因为我们需要从tws接受信息,所以实际上两行之间还要加点东西,用来告诉系统谁负责处理什么信息。
from ib.opt import ibConnection, message
con = ibConnection()
con.register(printData, message.historicalData)
con.connect()
注意增加的内容:1、多引入了一个message模块,里面有一个message类。historicalData是它的属性(这个不知道我理解的正确与否,我在message.py里找不到这个属性的定义)。总之
con.register(printData, message.historicalData)
这句就是告诉系统,如果收到关于historicalData的消息,由printData这个函数处理。注意你有可能会遇到一些sample代码,里面用的HistoricalData。这是版本问题。最新的0.8版是用的historicalData。
有时候你还需要定义一些其它的函数处理其他的消息类型,见下面的例子。
下一个步骤是定义contract。又得引用:
from ib.ext.Contract import Contract
引入contact这个类,创建一个对象:
    newContract = Contract()
修改里面的属性
    newContract.m_symbol = 'TSLA'
    newContract.m_secType = 'STK'
    newContract.m_exchange = 'SMART'
    newContract.m_currency = 'USD'
以下期权才用得到,对于股票就是如下设定:
    newContract.m_expiry = ''
    newContract.m_strike = 0.0
    newContract.m_right = ''
准备工作好了以后,读取历史数据的关键函数是
reqHistoricalData(tickerId, contract,
                          endDateTime, # last requested bar date/time
                          durationStr,  # quote duration, units: S,D,W,M,Y
                          barSizeSetting,  # bar length
                          whatToShow,  # what to show
                          useRTH, formatDate )
注意同一个ibConnection对象,tickerId不能重复,所以每次调用记得tickerId+1。如果close之后重新connect则tickerId清零。
下面用到的CSV文件读写我就不特别说明了,特别简单。
以下是我的代码。改编自Merlinson 的request historical data example.py。主要改动为两点:
1、时间计算:原来的代码bar length改成1天之后运行出错。
2、新读取数据与旧数据的合并:原来的版本如果今天白天运行过一次,在收盘后再运行一次,如果收盘后ask, bid, last任何一项出现变动,CSV文件里就会出现两条当日记录。这个问题相信已经得到解决。另外我的版本新数据在底,老数据在顶。
另外我加了简单50日均线和200日均线,偷懒作为返回函数值了……
欢迎测试和提供修改意见。
=============================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# requests historical data from TWS and can save to a CSV file
# bar length is set to 15 minute bars
# legal bar lengths: 1 secs, 5 secs, 10 secs, 15 secs, 30 secs, 1 min,
#     2 mins, 3 mins, 5 mins, 10 mins, 15 mins, 20 mins, 30 mins, 1 hour,
#     2 hours, 3 hours, 4 hours, 8 hours, 1 day, 1 week, 1 month
# the data duration is set to 1 year (28800 seconds)
# can read/write a CSV OHLC file
#   if the file exists, the first file date/time becomes the inital request
#   so earlier historical data is requested and put at the front of the file
# uses Eastern Standard Time for data requests and writing date/time to CSV
from ib.ext.Contract import Contract
from ib.opt import ibConnection, message
import os.path, time
from collections import OrderedDict
def contract(contractTuple):
    newContract = Contract()
    newContract.m_symbol = contractTuple[0]
    newContract.m_secType = contractTuple[1]
    newContract.m_exchange = contractTuple[2]
    newContract.m_currency = contractTuple[3]
    newContract.m_expiry = contractTuple[4]
    newContract.m_strike = contractTuple[5]
    newContract.m_right = contractTuple[6]
    print 'Contract Parameters: [%s,%s,%s,%s,%s,%s,%s]' % contractTuple
    return newContract
def cleanMerge(seq1,seq2):
    seen = set(seq1)
    if seq1[0].split(',')[3:6]==[0,0,0]:
        seen={x[0:10] for x in seen}
        seq1.extend([ x for x in seq2 if x[0:10] not in seen])
    else:
        seq1.extend([ x for x in seq2 if x not in seen])
    return seq1
def SMA200(seq):
    sum=0
    for i in range(1,201):
        sum+=float(seq[-i])
    return sum/200
def SMA50(seq):
    sum=0
    for i in range(1,51):
        sum+=float(seq[-i])
    return sum/50
# convert UTC to New York EST timezone
def ESTtime(msg):
    return time.gmtime(int(msg.date) - (5 - time.daylight)*3600)
def strDatetime(dt):
    return dt[0:4]+','+dt[4:6]+','+dt[6:8]+','+dt[10:12]+','+dt[13:15]+','+dt[16:18]
def printData(msg):
    if int(msg.high) > 0:
        dataStr =  '%s,%s,%s,%s,%s,%s' % (strDatetime(msg.date),
                                          msg.open,
                                          msg.high,
                                          msg.low,
                                          msg.close,
                                          msg.volume)
                                          #print dataStr
        if printData.write2file: printData.newRowData.append(dataStr+'\n')
    else: printData.finished = True
def watchAll(msg):
    print msg
def getIbHistData(sym, sec='STK', exch='SMART', barLength='1 day', duration='1 Y'):
    con = ibConnection()
    con.registerAll(watchAll)
    con.unregister(watchAll, message.historicalData)
    con.register(printData, message.historicalData)
    con.connect()
    time.sleep(1)
    contractTuple = (sym, sec, exch, 'USD', '', 0.0, '')
   
    endSecs = time.time()-(5-time.daylight)*60*60  # to NY EST via gmtime
    NYtime = time.gmtime(endSecs)
   
    # combined dateStr+timeStr format is 'YYYYMMDD hh:mm:ss TMZ'
    dateStr = time.strftime('%Y%m%d', NYtime)
    timeStr = time.strftime(' %H:%M:%S EST', NYtime)
   
    # write2file=True to write data to: fileName in the default directory
    printData.write2file = True
    if printData.write2file:
        directory="HistData"
        if not os.path.exists(directory):os.makedirs(directory)
        barLengthStr = barLength.replace(" ","_") # add the bar length to the file name
        fileName = directory+'/'+contractTuple[0]+'_'+contractTuple[1]+'_'+dateStr+'_'+barLengthStr+'.csv'
        if os.path.isfile(fileName): # found a previous version
            file = open(fileName, 'r')
            oldRowData = file.readlines()
            file.close()
            prevRec=len(oldRowData)
            if prevRec > 1:
                # get the new end date and time from the last data line of the file
                lastRow = oldRowData[-1]
                lastRowData=lastRow.split(",")
                endtimeStr = ' %s:%s:%s EST' % (lastRowData[3],lastRowData[4],lastRowData[5])
                if endtimeStr.find('::') :
                    if barLength=='1 day':
                        duration=str((int(dateStr[0:4])-int(lastRow[0:4]))*366+(int(dateStr[4:6])-int(lastRow[5:7]))*31+int(dateStr[6:8])-int(lastRow[8:10]))+' D'
                        print duration
                    else:
                        print "barlength too short"
                #barlength is in mins and previous data has time
                elif barLength.find('min')>0:
                    duration=str((int(dateStr[6:8])-int(lastRow[8:10]))*24*60+(int(timeStr[1:3])-int(lastRow[11:13]))*60+int(timeStr[4:6])-int(lastRow[14:16]))+' D'
                else:
                    print "other unit of time need more work"
   
        else:
            oldRowData = [] # and use default end date
            prevRec=0
            oldRowData.append('Year,Month,Day,Hour,Minute,Second,Open,High,Low,Close,Volume\n')
    printData.newRowData = []
   
    printData.finished = False # true when historical data is done
    print 'End Date/Time String: [%s]' % (dateStr+timeStr)
    con.reqHistoricalData(0,
                          contract(contractTuple),
                          dateStr+timeStr, # last requested bar date/time
                          duration,  # quote duration, units: S,D,W,M,Y
                          barLength,  # bar length
                          'TRADES',  # what to show
                          0, 1 )
    countSecs = 0
    while not printData.finished and countSecs < 20: # wait up to 20 seconds
        time.sleep(1)
        countSecs += 1
    con.disconnect()
    print 'CSV format: year,month,day,hour,minute,second,open,high,low,close,volume'
    if printData.write2file:
        Data=cleanMerge(oldRowData,printData.newRowData)
        file = open(fileName, 'w')
        file.writelines(Data)
        file.close()
        print len(Data)-prevRec,' of CSV data appended to file: ', fileName
    prices=[rec.split(",")[9] for rec in Data]
    return [SMA200(prices),SMA50(prices)]
if __name__ == "__main__":
#getIbHistData('COMP',sec='IND', exch='NASDAQ', duration='1 Y')
    getIbHistData('TSLA',duration='1 Y')

扩展阅读