cjm2671
cjm2671

Reputation: 19486

Python Custom Formatters

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?

Upvotes: 0

Views: 88

Answers (1)

aghast
aghast

Reputation: 15310

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')

Upvotes: 3

Related Questions