从上周末开始边学边做程序化交易。目标是每天开盘前做一下设置,然后就不用盯盘了。博客里就不讨论策略了,只讨论技术。我是python初学者,但是选择这个语言有几方面原因:它语言简洁;做数据处理比c++,java方便;比matlab便宜(免费),而且节省空间。另外正好借这个机会希望掌握这门语言。
以下假设你的机器已经安装了python 2.7.
google ibpy。去github或者google code下载,注意github的版本更新一些。而网上的一些例子用的是老版本。这个以后我会提到。把下载下来的包放到你想要的文件夹下后,在terminal里 sudo python setup.py install。
最近有点儿忙,只能每周一更了。
建立好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')