Open 3rock618 opened 5 years ago
In coding up the solution, I see the problem that the contract object that comes through the message handler doesn't have m_contractMonth embedded. In that case I would do this.
monthcode_3letter = {'JAN':'F','FEB':'G','MAR':'H','APR':'J','MAY':'K','JUN':'M',
'JUL':'N','AUG':'Q','SEP':'U','OCT':'V','NOV':'X','DEC':'Z'}
or however you would embed that into dataTypes
if localSymbol != "":
if ' ' not in localSymbol:
expMonth = localSymbol[-2:-1]
else:
expMonth = monthcode_3letter[localSymbol.split()[1]]
# expYear only good until 2028
expYear = localSymbol[-1]
exp = expMonth + [str(2020+int(expYear)), '2019'][expYear=='9']
replace this snippet with lines 1501-1503 in ezibpy.py
I ran a script with the current code and I got these as contractString
:
Are you sure there's a problem?
btw - using this code also produce the correct contractStrings (even with 2029+ as exp year)
...
elif contractTuple[1] == "FUT":
exp = str(contractTuple[4])[:6]
exp = dataTypes["MONTH_CODES"][int(exp[4:6])] + exp[:4]
contractString = (contractTuple[0] + exp, contractTuple[1])
elif contractTuple[1] == "CASH":
...
ok, down the rabbit hole we go! here is code to get contract strings for a list of futures that I use in my project.
A little about this code: I use the continuous future (CONTFUT
) lookup to get the correct front month at any given time, then I lookup the contract using that m_contractMonth
. Then, I use the contract object that is embedded in contract details (contract = contract_details['contracts'][0]
), because it comes with the correct m_conId
so I prefer to use that object when I'm making orders, etc.
from time import sleep
import ezibpy
tws = ezibpy.ezIBpy()
symref1 = {'PA':'NYMEX','GC':'NYMEX','AUD':'GLOBEX','CAD':'GLOBEX',
'JPY':'GLOBEX','SI':'NYMEX','HE':'GLOBEX','UB':'ECBOT',
'EMD':'GLOBEX','ZB':'ECBOT','ZN':'ECBOT','ZS':'ECBOT',
'CL':'NYMEX','ESTX50':'DTB','YM':'ECBOT','DAX':'DTB',
'HO':'NYMEX','LE':'GLOBEX','HG':'NYMEX','PL':'NYMEX',
'QM':'NYMEX','GBP':'GLOBEX','RTY':'GLOBEX','NQ':'GLOBEX',
'EUR':'GLOBEX','RB':'NYMEX','NG':'NYMEX','GBL':'DTB',
'ES':'Globex','NIY':'GLOBEX','BTP':'DTB','GBX':'DTB',
'SB':'NYBOT','QO':'NYMEX','CC':'NYBOT','CT':'NYBOT',
'KC':'NYBOT','DX':'NYBOT','CAC40':'MONEP','NKD':'GLOBEX','MNQ':'GLOBEX'}
tws.connect(clientId=101, host="localhost", port=7497)
tws.requestPositionUpdates(True)
print(f'{"sym":5}',f'{"|localsymbol":11}','|contract string')
for sym in symref1:
contfut_contract = tws.createContract((sym,"CONTFUT",symref1[sym],'','','',''))
while True:
tws.requestContractDetails(contfut_contract)
contfut_details = tws.contractDetails(contfut_contract)
if contfut_details['m_summary']['m_conId'] != 0: break
sleep(1)
print('retry make contfut contract')
if sym == 'DAX':
contract = tws.createContract((sym,'FUT',symref1[sym],'',contfut_details['m_contractMonth'],'','',25))
elif sym == 'NIY':
contract = tws.createContract((sym,'FUT',symref1[sym],'',contfut_details['m_contractMonth'],'','',500))
elif sym == 'CAC40':
contract = tws.createContract((sym,'FUT',symref1[sym],'',contfut_details['m_contractMonth'],'','',10))
else:
contract = tws.createContract((sym,'FUT',symref1[sym],'',contfut_details['m_contractMonth'],'',''))
while True:
tws.requestContractDetails(contract)
contract_details = tws.contractDetails(contract)
if contract_details['m_summary']['m_conId'] != 0: break
sleep(1)
print('retry make fut contract')
contract = contract_details['contracts'][0]
localSymbol = contract_details['m_summary']['m_localSymbol']
print(f'{sym:6}', f'{localSymbol:12}', tws.contractString(contract))
Here are the results:
sym |localsymbol |contract string
PA PAU9 PAU2019_FUT
GC GCQ9 GCQ2019_FUT
AUD 6AU9 AUDU2019_FUT
CAD 6CU9 CADU2019_FUT
JPY 6JU9 JPYU2019_FUT
SI SIN9 SIN2019_FUT
HE HEQ9 HEQ2019_FUT
UB UB SEP 19 UBU2019_FUT
EMD EMDM9 EMDD2019_FUT
ZB ZB SEP 19 ZBU2019_FUT
ZN ZN SEP 19 ZNU2019_FUT
ZS ZS JUL 19 ZSN2019_FUT
CL CLN9 CLN2019_FUT
ESTX50 FESX JUN 19 ESTX50S2019_FUT
YM YM JUN 19 YMM2019_FUT
DAX FDAX JUN 19 DAXA2019_FUT
HO HON9 HON2019_FUT
LE LEQ9 LEQ2019_FUT
HG HGN9 HGN2019_FUT
PL PLN9 PLN2019_FUT
QM QMN9 QMN2019_FUT
GBP 6BU9 GBPU2019_FUT
RTY RTYM9 RTYY2019_FUT
NQ NQM9 NQM2019_FUT
EUR 6EU9 EURU2019_FUT
RB RBN9 RBN2019_FUT
NG NGN9 NGN2019_FUT
GBL FGBL SEP 19 GBLB2019_FUT
ES ESM9 ESM2019_FUT
NIY NIYU9 NIYY2019_FUT
BTP FBTP SEP 19 BTPT2019_FUT
GBX FGBX SEP 19 GBXB2019_FUT
SB SBV9 SBV2019_FUT
QO QOQ9 QOQ2019_FUT
CC CCU9 CCU2019_FUT
CT CTZ9 CTZ2019_FUT
KC KCU9 KCU2019_FUT
DX DXU9 DXU2019_FUT
NKD NKDU9 NKDD2019_FUT
MNQ MNQU9 MNQQ2019_FUT
ESTX50, DAX, RTY, GBL, NIY, BTP, GBX are all wrong.
I also will add that I ran into some errors when I tried to run tws.requestContractDetails()
on DAX, NIY, and CAC40 without having a multiplier in there, which is why I had to run those as special cases. But that error looks like a separate issue, so I just worked around it for this example.
Ok... Here's my code:
import ezibpy
symbols = {
'PA': 'NYMEX', 'GC': 'NYMEX', 'AUD': 'GLOBEX', 'CAD': 'GLOBEX',
'JPY': 'GLOBEX', 'SI': 'NYMEX', 'HE': 'GLOBEX', 'UB': 'ECBOT',
'EMD': 'GLOBEX', 'ZB': 'ECBOT', 'ZN': 'ECBOT', 'ZS': 'ECBOT',
'CL': 'NYMEX', 'ESTX50': 'DTB', 'YM': 'ECBOT', 'DAX': 'DTB',
'HO': 'NYMEX', 'LE': 'GLOBEX', 'HG': 'NYMEX', 'PL': 'NYMEX',
'QM': 'NYMEX', 'GBP': 'GLOBEX', 'RTY': 'GLOBEX', 'NQ': 'GLOBEX',
'EUR': 'GLOBEX', 'RB': 'NYMEX', 'NG': 'NYMEX', 'GBL': 'DTB',
'ES': 'Globex', 'NIY': 'GLOBEX', 'BTP': 'DTB', 'GBX': 'DTB',
'SB': 'NYBOT', 'QO': 'NYMEX', 'CC': 'NYBOT', 'CT': 'NYBOT',
'KC': 'NYBOT', 'DX': 'NYBOT', 'CAC40': 'MONEP', 'NKD': 'GLOBEX',
'MNQ': 'GLOBEX'
}
ibc = ezibpy.ezIBpy()
ibc.connect(clientId=100, port=7497)
for symbol in symbols:
contfut_contract = ibc.createContract(
(symbol, "CONTFUT", symbols[symbol], '', '', '', ''))
expiry = ibc.contractDetails(contfut_contract)['m_contractMonth']
contract = ibc.createFuturesContract(
symbol, exchange=symbols[symbol], expiry=expiry)
contractString = ibc.contractString(contract)
print(contractString)
ibc.disconnect()
Here's the output:
Server Version: 76
TWS Time at connection:20190616 10:34:27 IST
PAU2019_FUT
GCQ2019_FUT
AUDU2019_FUT
CADU2019_FUT
JPYU2019_FUT
SIN2019_FUT
HEQ2019_FUT
UBU2019_FUT
EMDM2019_FUT
ZBU2019_FUT
ZNU2019_FUT
ZSN2019_FUT
CLN2019_FUT
ESTX50M2019_FUT
YMM2019_FUT
DAXM2019_FUT
HON2019_FUT
LEQ2019_FUT
HGN2019_FUT
PLN2019_FUT
QMQ2019_FUT
GBPU2019_FUT
RTYM2019_FUT
NQM2019_FUT
EURU2019_FUT
RBN2019_FUT
NGN2019_FUT
GBLU2019_FUT
ESM2019_FUT
NIYU2019_FUT
BTPU2019_FUT
GBXU2019_FUT
SBV2019_FUT
QOQ2019_FUT
CCU2019_FUT
CTZ2019_FUT
KCU2019_FUT
DXU2019_FUT
CAC40M2019_FUT
NKDU2019_FUT
MNQU2019_FUT
Looks ok to me. Have I missed something?
I'm using this version:
...
elif contractTuple[1] == "FUT":
exp = str(contractTuple[4])[:6]
exp = dataTypes["MONTH_CODES"][int(exp[4:6])] + exp[:4]
contractString = (contractTuple[0] + exp, contractTuple[1])
elif contractTuple[1] == "CASH":
...
This is the version I have - ezibpy.py lines 1497-1509
...
elif contractTuple[1] == "FUT":
exp = ' ' # default
# round expiry day to expiry month
if localSymbol != "":
# exp = localSymbol[2:3]+str(contractTuple[4][:4])
exp = localSymbol[2:3] + self.localSymbolExpiry[localSymbol][:4]
if ' ' in exp:
exp = str(contractTuple[4])[:6]
exp = dataTypes["MONTH_CODES"][int(exp[4:6])] + str(int(exp[:4]))
contractString = (contractTuple[0] + exp, contractTuple[1])
...
One problem with your code is that one MUST check that the returned contract object found a valid match. One easy way to do that is details['m_summary']['m_conId'] != 0
if it returns 0 then IB did not find a match, which will lead to much bigger problems when you try to place an order.
To highlight this, if you run your code above on ESTX50 you'll find that ibc.createFuturesContract()
yields m_conId = 0
. If you don't check that and you then try ibc.placeOrder(contract, ibc.createOrder(1))
IB will go and order one contract of ES.
Adjustments I made to your code:
1) added assert statement to check m_conId
2) non-USD futures needed explicit currencies to return a valid contract
3) some contracts REQUIRED multipliers to return a valid contract
With those changes - everything works as it should.
import ezibpy
from time import sleep
symbols = {
'ZS' :'ECBOT' ,'ZB' :'ECBOT' ,'YM' :'ECBOT' ,'UB' :'ECBOT' ,'ZN' :'ECBOT' ,
'LE' :'GLOBEX','NKD' :'GLOBEX','NQ' :'GLOBEX','RTY' :'GLOBEX','MNQ':'GLOBEX',
'HE' :'GLOBEX','AUD' :'GLOBEX','JPY':'GLOBEX','GBP' :'GLOBEX','EUR':'GLOBEX',
'CAD':'GLOBEX','EMD' :'GLOBEX','ES' :'GLOBEX','KC' :'NYBOT' ,'DX' :'NYBOT' ,
'CT' :'NYBOT' ,'CC' :'NYBOT' ,'SB' :'NYBOT' ,'CL' :'NYMEX' ,'HG' :'NYMEX' ,
'QO' :'NYMEX' ,'QM' :'NYMEX' ,'GC' :'NYMEX' ,'PA' :'NYMEX' ,'NG' :'NYMEX' ,
'HO' :'NYMEX' ,'RB' :'NYMEX' ,'PL' :'NYMEX' ,
'BTP':'DTB' ,'GBL' :'DTB' ,'GBX':'DTB' ,'ESTX50':'DTB' , # in Euros
'NIY':'GLOBEX','CAC40':'MONEP' ,'SI' :'NYMEX' ,'DAX' :'DTB' , # multipliers needed
}
ibc = ezibpy.ezIBpy()
ibc.connect(clientId=101, host="localhost", port=7497)
sleep(1)
for symbol in symbols:
contfut_contract = ibc.createContract(
(symbol, "CONTFUT", symbols[symbol], '', '', '', ''))
expiry = ibc.contractDetails(contfut_contract)['m_contractMonth']
currency = ibc.contractDetails(contfut_contract)['m_summary']['m_currency']
mult = ibc.contractDetails(contfut_contract)['m_summary']['m_multiplier']
assert ibc.contractDetails(contfut_contract)['m_summary']['m_conId'] != 0
contract = ibc.createContract(
(symbol,'FUT',symbols[symbol],currency,expiry,'','',mult))
assert ibc.contractDetails(contract)['m_summary']['m_conId'] != 0
contractString = ibc.contractString(contract)
print (contractString)
So far so good.
However, there's still one thing that's a little dicey. We've seen that ibc.contractDetails(contract)['m_summary']['m_conId'] != 0
that's great. But if we look at the actual contract object we see that contract['m_conId'] = 0
So that is strange, and perhaps even a little dangerous. But not to worry, what looks to be an exact copy of the contract object is embedded in the details. ibc.contractDetails(contract)['contracts'][0]
the benefit of using this contract object to place orders and such is that the m_conId
is not 0, it's the true m_conId
.
But if use that contract object to place an order, because it's safer to have the correct m_conId
, this is where we find the problem.
contract_emb = ibc.contractDetails(contract)['contracts'][0]
contractString2 = ibc.contractString(contract_emb)
print(f'{symbol:6}', f'{localSymbol:11}', f'{contractString:15}', f'{contractString2:15}',
contractString==contractString2)
and the results:
ZS ZS JUL 19 ZSN2019_FUT ZSN2019_FUT True
ZB ZB SEP 19 ZBU2019_FUT ZBU2019_FUT True
YM YM SEP 19 YMU2019_FUT YMU2019_FUT True
UB UB SEP 19 UBU2019_FUT UBU2019_FUT True
ZN ZN SEP 19 ZNU2019_FUT ZNU2019_FUT True
LE LEQ9 LEQ2019_FUT LEQ2019_FUT True
NKD NKDU9 NKDU2019_FUT NKDD2019_FUT False
NQ NQU9 NQU2019_FUT NQU2019_FUT True
RTY RTYM9 RTYM2019_FUT RTYY2019_FUT False
MNQ MNQU9 MNQU2019_FUT MNQQ2019_FUT False
HE HEQ9 HEQ2019_FUT HEQ2019_FUT True
AUD 6AU9 AUDU2019_FUT AUDU2019_FUT True
JPY 6JU9 JPYU2019_FUT JPYU2019_FUT True
GBP 6BU9 GBPU2019_FUT GBPU2019_FUT True
EUR 6EU9 EURU2019_FUT EURU2019_FUT True
CAD 6CU9 CADU2019_FUT CADU2019_FUT True
EMD EMDU9 EMDU2019_FUT EMDD2019_FUT False
ES ESU9 ESU2019_FUT ESU2019_FUT True
KC KCU9 KCU2019_FUT KCU2019_FUT True
DX DXU9 DXU2019_FUT DXU2019_FUT True
CT CTZ9 CTZ2019_FUT CTZ2019_FUT True
CC CCU9 CCU2019_FUT CCU2019_FUT True
SB SBV9 SBV2019_FUT SBV2019_FUT True
CL CLQ9 CLQ2019_FUT CLQ2019_FUT True
HG HGN9 HGN2019_FUT HGN2019_FUT True
QO QOQ9 QOQ2019_FUT QOQ2019_FUT True
QM QMQ9 QMQ2019_FUT QMQ2019_FUT True
GC GCQ9 GCQ2019_FUT GCQ2019_FUT True
PA PAU9 PAU2019_FUT PAU2019_FUT True
NG NGN9 NGN2019_FUT NGN2019_FUT True
HO HON9 HON2019_FUT HON2019_FUT True
RB RBN9 RBN2019_FUT RBN2019_FUT True
PL PLN9 PLN2019_FUT PLN2019_FUT True
BTP FBTP SEP 19 BTPU2019_FUT BTPT2019_FUT False
GBL FGBL SEP 19 GBLU2019_FUT GBLB2019_FUT False
GBX FGBX SEP 19 GBXU2019_FUT GBXB2019_FUT False
ESTX50 FESX JUN 19 ESTX50M2019_FUT ESTX50S2019_FUT False
NIY NIYU9 NIYU2019_FUT NIYY2019_FUT False
CAC40 FCEM9 CAC40M2019_FUT CAC40E2019_FUT False
SI SIN9 SIN2019_FUT SIN2019_FUT True
DAX FDAX JUN 19 DAXM2019_FUT DAXA2019_FUT False
The difference between contract
and contract_emb
:
>>> pp(contract.__dict__)
{'m_conId': 0,
'm_currency': 'USD',
'm_exchange': 'GLOBEX',
'm_expiry': '201909',
'm_includeExpired': True,
'm_multiplier': '5',
'm_right': '',
'm_secType': 'FUT',
'm_strike': '',
'm_symbol': 'NKD'}
>>> pp(contract_emb.__dict__)
{'m_conId': 333153064,
'm_currency': 'USD',
'm_exchange': 'GLOBEX',
'm_expiry': '20190912',
'm_includeExpired': False,
'm_localSymbol': 'NKDU9',
'm_multiplier': '5',
'm_primaryExch': None,
'm_right': None,
'm_secType': 'FUT',
'm_strike': 0.0,
'm_symbol': 'NKD',
'm_tradingClass': 'NKD'}
I hope it makes sense why I would prefer to work with the latter object rather than the former. I hope this isn't too nitpicky. But IB can be very particular, so I want to do things in the safest way.
If I use the contract_emb
object to place an order, tws.positions returns the misformed contract strings and it makes it hard to discover my position for other logic.
The latest version (from github - still not on pypi) introduced createContinuousFuturesContract()
.
Using this version, I ran this script:
#!/usr/bin/env python
import ezibpy
symbols = {
'ZS': 'ECBOT', 'ZB': 'ECBOT', 'YM': 'ECBOT', 'UB': 'ECBOT', 'ZN': 'ECBOT',
'LE': 'GLOBEX', 'NKD': 'GLOBEX', 'NQ': 'GLOBEX', 'RTY': 'GLOBEX', 'MNQ': 'GLOBEX',
'HE': 'GLOBEX', 'AUD': 'GLOBEX', 'JPY': 'GLOBEX', 'GBP': 'GLOBEX', 'EUR': 'GLOBEX',
'CAD': 'GLOBEX', 'EMD': 'GLOBEX', 'ES': 'GLOBEX', 'KC': 'NYBOT', 'DX': 'NYBOT',
'CT': 'NYBOT', 'CC': 'NYBOT', 'SB': 'NYBOT', 'CL': 'NYMEX', 'HG': 'NYMEX',
'QO': 'NYMEX', 'QM': 'NYMEX', 'GC': 'NYMEX', 'PA': 'NYMEX', 'NG': 'NYMEX',
'HO': 'NYMEX', 'RB': 'NYMEX', 'PL': 'NYMEX',
'BTP': 'DTB', 'GBL': 'DTB', 'GBX': 'DTB', 'ESTX50': 'DTB', # in Euros
'NIY': 'GLOBEX', 'CAC40': 'MONEP', 'SI': 'NYMEX', 'DAX': 'DTB', # multipliers needed
}
ibc = ezibpy.ezIBpy()
ibc.connect(clientId=100, port=7497)
for symbol in symbols:
contract = ibc.createContinuousFuturesContract(symbol, symbols[symbol])
contractString = ibc.contractString(contract)
localSymbol = ibc.contractDetails(contract)["m_summary"]['m_localSymbol']
print(symbol, "\t", localSymbol, "\t", contractString)
ibc.disconnect()
...and I got this:
ZS ZS JUL 19 ZSN2019_FUT
ZB ZB SEP 19 ZBU2019_FUT
YM YM SEP 19 YMU2019_FUT
UB UB SEP 19 UBU2019_FUT
ZN ZN SEP 19 ZNU2019_FUT
LE LEQ9 LEQ2019_FUT
NKD NKDU9 NKDU2019_FUT
NQ NQU9 NQU2019_FUT
RTY RTYM9 RTYM2019_FUT
MNQ MNQU9 MNQU2019_FUT
HE HEQ9 HEQ2019_FUT
AUD 6AU9 AUDU2019_FUT
JPY 6JU9 JPYU2019_FUT
GBP 6BU9 GBPU2019_FUT
EUR 6EU9 EURU2019_FUT
CAD 6CU9 CADU2019_FUT
EMD EMDU9 EMDU2019_FUT
ES ESU9 ESU2019_FUT
KC KCU9 KCU2019_FUT
DX DXU9 DXU2019_FUT
CT CTZ9 CTZ2019_FUT
CC CCU9 CCU2019_FUT
SB SBV9 SBV2019_FUT
CL CLQ9 CLQ2019_FUT
HG HGN9 HGN2019_FUT
QO QOQ9 QOQ2019_FUT
QM QMQ9 QMQ2019_FUT
GC GCQ9 GCQ2019_FUT
PA PAU9 PAU2019_FUT
NG NGN9 NGN2019_FUT
HO HON9 HON2019_FUT
RB RBN9 RBN2019_FUT
PL PLN9 PLN2019_FUT
BTP FBTP SEP 19 BTPU2019_FUT
GBL FGBL SEP 19 GBLU2019_FUT
GBX FGBX SEP 19 GBXU2019_FUT
ESTX50 FESX JUN 19 ESTX50M2019_FUT
NIY NIYU9 NIYU2019_FUT
CAC40 FCEM9 CAC40M2019_FUT
SI SIN9 SIN2019_FUT
DAX FDAX JUN 19 DAXM2019_FUT
This is quite an improvement. And there is still one issue related to the m_expiry
vs the m_contractMonth
. The m_contractMonth
should always correspond to the correct letter code, but if you try to use m_expiry
it will sometimes mismatch, as I've highlighted below
symbols = {
# GLOBEX
'NQ' :'GLOBEX','RTY':'GLOBEX','ES' :'GLOBEX','MNQ':'GLOBEX','LE' :'GLOBEX',
'HE' :'GLOBEX','AUD':'GLOBEX','JPY':'GLOBEX','GBP':'GLOBEX','EUR':'GLOBEX',
'CAD':'GLOBEX','EMD':'GLOBEX','NKD':'GLOBEX',
# NYBOT
'KC' :'NYBOT' ,'DX' :'NYBOT' ,'CT' :'NYBOT' ,'CC' :'NYBOT' ,'SB' :'NYBOT' ,
# NYMEX
'CL' :'NYMEX' ,'HG' :'NYMEX' ,'QO' :'NYMEX' ,'QM' :'NYMEX' ,'GC' :'NYMEX' ,
'HO' :'NYMEX' ,'RB' :'NYMEX' ,'PL' :'NYMEX' ,'PA' :'NYMEX' ,'NG' :'NYMEX' ,
# ECBOT
'ZS' :'ECBOT' ,'ZB' :'ECBOT' ,'YM' :'ECBOT' ,'UB' :'ECBOT' ,'ZN' :'ECBOT' ,
# DTB (Euros)
'BTP':'DTB' ,'GBL':'DTB' ,'GBX':'DTB' ,'ESTX50':'DTB',
# Multipliers needed
'NIY':'GLOBEX','SI' :'NYMEX' ,'DAX':'DTB' ,'CAC40' :'MONEP',
}
import ezibpy
from time import sleep
ibc = ezibpy.ezIBpy()
ibc.connect(clientId=101, host="localhost", port=7497)
for symbol in symbols.index:
contract = ibc.createContinuousFuturesContract(symbol,symbols[symbol])
contract_emb = ibc.contractDetails(contract)['contracts'][0]
contractString = ibc.contractString(contract)
contractString2 = ibc.contractString(contract_emb)
localSymbol = ibc.contractDetails(contract)['m_summary']['m_localSymbol']
expiry = ibc.contractDetails(contract)['m_summary']['m_expiry'][:6]
contractMonth = ibc.contractDetails(contract)['m_contractMonth']
print(f'{symbol:6}', f'{localSymbol:11}', f'{contractString:15}', f'{contractString2:15}',
contractString==contractString2, expiry==contractMonth)
results:
NQ NQU9 NQU2019_FUT NQU2019_FUT True True
RTY RTYM9 RTYM2019_FUT RTYM2019_FUT True True
ES ESU9 ESU2019_FUT ESU2019_FUT True True
MNQ MNQU9 MNQU2019_FUT MNQU2019_FUT True True
LE LEQ9 LEQ2019_FUT LEQ2019_FUT True True
HE HEQ9 HEQ2019_FUT HEQ2019_FUT True True
AUD 6AU9 AUDU2019_FUT AUDU2019_FUT True True
JPY 6JU9 JPYU2019_FUT JPYU2019_FUT True True
GBP 6BU9 GBPU2019_FUT GBPU2019_FUT True True
EUR 6EU9 EURU2019_FUT EURU2019_FUT True True
CAD 6CU9 CADU2019_FUT CADU2019_FUT True True
EMD EMDU9 EMDU2019_FUT EMDU2019_FUT True True
NKD NKDU9 NKDU2019_FUT NKDU2019_FUT True True
KC KCU9 KCU2019_FUT KCU2019_FUT True True
DX DXU9 DXU2019_FUT DXU2019_FUT True True
CT CTZ9 CTZ2019_FUT CTZ2019_FUT True True
CC CCU9 CCU2019_FUT CCU2019_FUT True True
SB SBV9 SBV2019_FUT SBU2019_FUT False False
CL CLQ9 CLQ2019_FUT CLN2019_FUT False False
HG HGN9 HGN2019_FUT HGN2019_FUT True True
QO QOQ9 QOQ2019_FUT QON2019_FUT False False
QM QMQ9 QMQ2019_FUT QMN2019_FUT False False
GC GCQ9 GCQ2019_FUT GCQ2019_FUT True True
HO HON9 HON2019_FUT HOM2019_FUT False False
RB RBN9 RBN2019_FUT RBM2019_FUT False False
PL PLN9 PLN2019_FUT PLN2019_FUT True True
PA PAU9 PAU2019_FUT PAU2019_FUT True True
NG NGN9 NGN2019_FUT NGM2019_FUT False False
ZS ZS JUL 19 ZSN2019_FUT ZSN2019_FUT True True
ZB ZB SEP 19 ZBU2019_FUT ZBU2019_FUT True True
YM YM SEP 19 YMU2019_FUT YMU2019_FUT True True
UB UB SEP 19 UBU2019_FUT UBU2019_FUT True True
ZN ZN SEP 19 ZNU2019_FUT ZNU2019_FUT True True
BTP FBTP SEP 19 BTPU2019_FUT BTPU2019_FUT True True
GBL FGBL SEP 19 GBLU2019_FUT GBLU2019_FUT True True
GBX FGBX SEP 19 GBXU2019_FUT GBXU2019_FUT True True
ESTX50 FESX JUN 19 ESTX50M2019_FUT ESTX50M2019_FUT True True
NIY NIYU9 NIYU2019_FUT NIYU2019_FUT True True
SI SIN9 SIN2019_FUT SIN2019_FUT True True
DAX FDAX JUN 19 DAXM2019_FUT DAXM2019_FUT True True
CAC40 FCEM9 CAC40M2019_FUT CAC40M2019_FUT True True
I'm not sure why are you looking into ibc.contractDetails(contract)['contracts'][0]
...
Using CL/GLOBEX
...
contract
returns CLQ2019_FUT
(Aug 2019) which is the correct continuous contract as specified by IB ibc.contractDetails(contract)['contracts'][0]
returns CLN2019_FUT
(Jul 2019) which is the closest-expiring contract you can still trade.I'm not sure why IB does that for some contracts, while for others it returns the same expiration for both contract
and ibc.contractDetails(contract)['contracts'][0]
, but the new method's purpose is to return the continuous contract when using ibc.createContinuousFuturesContract(symbol, exchange)
-- and it's doing it,
Unless you can prove me wrong, I'm closing this issue for now and pushing this version to PyPi 😁
The reason I use it is because for whatever reason, the ibc.contractDetails(contract)['contracts'][0]
object has m_conId
(non-zero) number assigned:
>>> pp(contract.__dict__)
{'m_conId': 0,
'm_currency': 'USD',
'm_exchange': 'NYMEX',
'm_expiry': '201908',
'm_includeExpired': True,
'm_multiplier': 1000,
'm_right': '',
'm_secType': 'FUT',
'm_strike': 0.0,
'm_symbol': 'CL'}
>>> pp(ibc.contractDetails(contract)['contracts'][0].__dict__)
{'m_conId': 138979269,
'm_currency': 'USD',
'm_exchange': 'NYMEX',
'm_expiry': '20190722',
'm_includeExpired': False,
'm_localSymbol': 'CLQ9',
'm_multiplier': '1000',
'm_primaryExch': None,
'm_right': None,
'm_secType': 'FUT',
'm_strike': 0.0,
'm_symbol': 'CL',
'm_tradingClass': 'CL'}
You'll also notice that the first object has m_expiry
as a 6-digit datestring YYYYMM, and the second object has m_expiry
as an 8-digit datestring YYYYMMDD.
This is what's causing the confusion for ibc.contractString()
--- 201908 vs 20190722 --- this is why the second object returns the incorrect contractString CLN2019_FUT (July instead of August).
Technically speaking, m_contractMonth: 201908
and m_expiry: 20190722
is the correct way to define the contract.
If you want to truly standardize the nomenclature m_expiry
should always be an 8-digit datestring YYYYMMDD and m_contractMonth
should always be a 6-digit datestring YYYYMM. and ibc.contractString()
should only ever be looking at the 6-digit datestring - m_contractMonth
Does this make sense?
(IB will let you search for a contract with a 6-digit YYYYMM string in expiry search field, and is smart enough to figure out that you're searching for a contract month and not an 8-digit expiry date, but it's not technically correct to call that datestring m_expiry
)
In my mind, the best way to solve this issue (and make the whole platform more robust) is the following:
1) Any time the function ibc.createContract()
is called (or any of the createContract analogous functions), the returned object should have ALWAYS return a non-zero m_conId
. Or else return a warning - IB did not find a match. (This should hold true for all sectype's - CASH OPT STK FUT etc.)
2) For futures contracts the returned object should contain EITHER
a) only a only 6-digit YYYYMM m_contractMonth
.
or
b) BOTH a 6-digit YYYYMM m_contractMonth
AND an 8-digit YYYYMMDD m_expiry
There are a few issues with the formula contractString for futures. on line 1480 you have
localSymbol = contract.m_localSymbol
.There are two problems here:
1) some exchanges use a different syntax for m_localSymbol for example DAX which trades on DTB
details['m_localSymbol'] = 'FDAX JUN 19'
. Or the Dow Jones (YM trading on ECBOT) hasdetails['m_localSymbol'] = 'YM JUN 19'
. On line 1503 you have the codeexp = localSymbol[2:3] + self.localSymbolExpiry[localSymbol][:4]
. Thus the first term (localSymbol[2:3]
) in the DAX would be "A" and in "YM" would be " " (for which you have a backup).2) for symbols that have more than 2-character symbols, for example, RTY, localSymbol = "RTYM9" again the term
localSymbol[2:3]
is inappropriate here.As this is so important for futures, this is what I would recommend:
From what I can tell
details['m_contractMonth']
seems to be very reliable, slicing [-2:] would give you the best chance to get the correct letter code for expiry. If that's empty, then I would look to localSymbol, first checking for spaces. If there is no space uselocalSymbol[-2:-1]
. If there are spaces, I would try to parse it 'localSymbol.split()[1]' and then use that to lookup against 3-letter month codes.Unfortunately, if you want to standardize to using the expiry letter codes (which IS the correct choice), using m_expiry will sometimes lead you astray because the expiry date does not always happen in the month of coded expiration, for example, July gasoline m_expiry is 6/28. Many of the single month contracts have this problem. So mixing expiration day and expiration month can be confusing.