ranaroussi / ezibpy

ezIBpy, a Pythonic Client for Interactive Brokers API
Apache License 2.0
324 stars 116 forks source link

Futures Contract String #31

Open 3rock618 opened 5 years ago

3rock618 commented 5 years ago

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) has details['m_localSymbol'] = 'YM JUN 19'. On line 1503 you have the code exp = 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 use localSymbol[-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.

3rock618 commented 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

ranaroussi commented 5 years ago

I ran a script with the current code and I got these as contractString:

Are you sure there's a problem?

ranaroussi commented 5 years ago

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":
...
3rock618 commented 5 years ago

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.

ranaroussi commented 5 years ago

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":
...
3rock618 commented 5 years ago

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.

ranaroussi commented 5 years ago

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
3rock618 commented 5 years ago

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
ranaroussi commented 5 years ago

I'm not sure why are you looking into ibc.contractDetails(contract)['contracts'][0]...

Using CL/GLOBEX...

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 😁

3rock618 commented 5 years ago

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