0

I'm writing a program that processes contracts that expire different months.

For example, a corn futures contract which expires in December 2016 would be called:

CZ2016

where C means 'corn', Z means December (it's custom).

Internally I represent everything as (year, month).

Each contract type has a different format, and ideally I need to be able to go in both directions.

Currently I have written some elaborate string processing to do this, but it's a mess.

What is the most pythonic way to do this?

2
  • 1
    personally - I would write a __format__ magic method (assuming that each contract is represented by a instance of a custom class. That way you can simply use print to ouput the write format. Commented May 26, 2016 at 13:05
  • 1
    Create a class with a c'tor that takes a string, and a __str__ method to return a similar string? Commented May 26, 2016 at 13:06

1 Answer 1

3

Here's something quick(ish):

#!python3
import collections
import time

class FuturesContract:
    Commodity = collections.namedtuple('Commodity',
            'symbol future exchange delivery_months')

    Commodities = [
        # Source: http://www.mrci.com/web/online-explanation-pages/symbols-a-codes.html
        # !IMPORTANT! Sorted by length of symbol!!!
        Commodity('ITCO', 'Brent Crude', 'ICE', 'F,G,H,J,K,M,N,Q,U,V,X,Z'),
        Commodity('AD', 'Australian Dollar', 'CME', 'H,M,U,Z'),
        Commodity('BP', 'British Pound', 'CME', 'H,M,U,Z'),
        Commodity('CD', 'Canadian Dollar', 'CME', 'H,M,U,Z'),
        Commodity('DX', 'US Dollar Index', 'ICE', 'H,M,U,Z'),
        Commodity('EU', 'EuroFx', 'CME', 'H,M,U,Z'),
        Commodity('JY', 'Japanese Yen', 'CME', 'H,M,U,Z'),
        Commodity('SF', 'Swiss Franc', 'CME', 'H,M,U,Z'),
        Commodity('CL', 'Crude Oil', 'NYM', 'F,G,H,J,K,M,N,Q,U,V,X,Z'),
        Commodity('HO', 'NY Harbor ULSD/Heating Oil', 'NYM', 'F,G,H,J,K,M,N,Q,U,V,X,Z'),
        Commodity('HU', 'Unleaded Gas', 'NYM', 'F,G,H,J,K,M,N,Q,U,V,X,Z'),
        Commodity('NG', 'Natural Gas', 'NYM', 'F,G,H,J,K,M,N,Q,U,V,X,Z'),
        Commodity('RB', 'RBOB Gasoline', 'NYM', 'F,G,H,J,K,M,N,Q,U,V,X,Z'),
        Commodity('BO', 'Soybean Oil', 'CBOT', 'F,H,K,N,Q,U,V,Z'),
        Commodity('KW', 'Kansas City Wheat', 'KCBT', 'H,K,N,U,Z'),
        Commodity('MW', 'Minneapolis Wheat', 'MGE', 'H,K,N,U,Z'),
        Commodity('SM', 'Soybean Meal', 'CBOT', 'F,H,K,N,Q,U,V,Z'),
        Commodity('DJ', 'Dow Jones Industrials', 'CBOT', 'H,M,U,Z'),
        Commodity('KV', 'Value Line (Discontinued)', 'KCBT', 'H,M,U,Z'),
        Commodity('MV', 'Value Line (Mini)', 'KCBT', 'H,M,U,Z'),
        Commodity('ND', 'Nasdaq-100', 'CME', 'H,M,U,Z'),
        Commodity('RL', 'Russell 2000 (Discontinued)', 'CME', 'H,M,U,Z'),
        Commodity('SP', 'S & P 500', 'CME', 'H,M,U,Z'),
        Commodity('YU', 'NYSE Composite (Discontinued)', 'NYFE', 'H,M,U,Z'),
        Commodity('ED', 'Eurodollars', 'CME', 'H,M,U,Z'),
        Commodity('FV', '5-Yr T-Notes', 'CBOT', 'H,M,U,Z'),
        Commodity('MB', 'Municipal Bonds', 'CBOT', 'H,M,U,Z'),
        Commodity('TU', '2-Yr T-Notes', 'CBOT', 'H,M,U,Z'),
        Commodity('TY', '10-Yr T-Notes', 'CBOT', 'H,M,U,Z'),
        Commodity('US', '30-Yr T-Bonds', 'CBOT', 'H,M,U,Z'),
        Commodity('FC', 'Feeder Cattle', 'CME', 'F,H,J,K,Q,U,V,X'),
        Commodity('LC', 'Live Cattle', 'CME', 'G,J,M,Q,V,Z'),
        Commodity('LH', 'Lean Hogs', 'CME', 'G,J,K,M,N,Q,V,Z'),
        Commodity('LE', 'Lean Hogs', 'CME', 'G,J,K,M,N,Q,V,Z'),
        Commodity('PB', 'Pork Bellies', 'CME', 'G,H,K,N,Q'),
        Commodity('DA', 'Milk Class III', 'CME', 'F,G,H,J,K,M,N,Q,U,V,X,Z'),
        Commodity('GC', 'Gold', 'CMX', 'G,J,M,Q,V,Z'),
        Commodity('HG', 'Copper', 'CMX', 'H,K,N,U,Z'),
        Commodity('PL', 'Platinum', 'NYM', 'F,J,N,V'),
        Commodity('SI', 'Silver', 'CMX', 'H,K,N,U,Z'),
        Commodity('RR', 'Rice', 'CBOT', 'F,H,K,N,U,X'),
        Commodity('CC', 'Cocoa', 'ICE', 'H,K,N,U,Z'),
        Commodity('CT', 'Cotton', 'ICE', 'H,K,N,V,Z'),
        Commodity('KC', 'Coffee', 'ICE', 'H,K,N,U,Z'),
        Commodity('LB', 'Lumber', 'CME', 'F,H,K,N,U,X'),
        Commodity('JO', 'Orange Juice', 'ICE', 'F,H,K,N,U,X'),
        Commodity('SB', 'Sugar #11', 'ICE', 'H,K,N,V'),
        Commodity('C', 'Corn', 'CBOT', 'F,H,K,N,U,X,Z'),
        Commodity('O', 'Oats', 'CBOT', 'H,K,N,U,Z'),
        Commodity('S', 'Soybeans', 'CBOT', 'F,H,K,N,Q,U,X'),
        Commodity('W', 'Wheat', 'CBOT', 'H,K,N,U,Z'),
    ]

    Months = {
        1: 'January',
        2: 'February',
        3: 'March',
        4: 'April',
        5: 'May',
        6: 'June',
        7: 'July',
        8: 'August',
        9: 'September',
        10: 'October',
        11: 'November',
        12: 'December',
        'F': 'January',
        'G': 'February',
        'H': 'March',
        'J': 'April',
        'K': 'May',
        'M': 'June',
        'N': 'July',
        'Q': 'August',
        'U': 'September',
        'V': 'October',
        'X': 'November',
        'Z': 'December',
    }

    MonthCodes = {
        'January': 'F',
        'February': 'G',
        'March': 'H',
        'April': 'J',
        'May': 'K',
        'June': 'M',
        'July': 'N',
        'August': 'Q',
        'September': 'U',
        'October': 'V',
        'November': 'X',
        'December': 'Z',
    }

    def __init__(self, details=None):
        if details is None:
            now = time.localtime()
            month = now.tm_mon
            year = now.tm_year
            self.commodity = None
        else:
            for c in self.Commodities:
                if details.startswith(c.symbol):
                    self.commodity = c
                    symlen = len(c.symbol)
                    month = details[symlen:symlen+1]
                    year = details[symlen+1:]
                    if len(year) == 2:
                        year = '20'+year
                    year = int(year)
                    break
            else:
                raise ValueError("Unparseable details given: "+details)

        month = self.Months[month]
        if self.commodity is not None:
            month_code = self.MonthCodes[month]
            if month_code not in self.commodity.delivery_months:
                raise ValueError('Invalid delivery month for commodity: '+month)

        self.month = month
        self.year = year

    def __str__(self):
        c = self.commodity
        return 'Unspecified' if c is None else c.symbol + self.MonthCodes[self.month] + str(self.year)


    def explain(self):
        c = self.commodity
        return [ 'A FuturesContract for:',
                '\t{}: {} (on the {} exchange)'.format(c.symbol, c.future, c.exchange),
                '\tDated: {} ({}), {}'.format(self.month, self.MonthCodes[self.month], self.year),
                ]

cz2016 = FuturesContract('CZ2016')

print('\n'.join(cz2016.explain()))
print('\n')
print('Commodity:', cz2016.commodity)
print('Month:', cz2016.month)
print('Year:', cz2016.year)

print("\nHas __str__:", cz2016)

print("\n\n")
print("Future LBQ2014: (should be invalid, because Q):")
fc = FuturesContract('LBQ2014')
Sign up to request clarification or add additional context in comments.

1 Comment

Oh my goodness, this is amazing! Thank you! Tell me you didn't write this just for this question? :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.