goldmansachs / gs-quant

Python toolkit for quantitative finance
https://developer.gs.com/discover/products/gs-quant/
Apache License 2.0
6.21k stars 784 forks source link

Unable to roundtrip Portfolio.to_csv -> Portfolio.from_csv #268

Open AlexanderJHall opened 1 year ago

AlexanderJHall commented 1 year ago

Describe the bug A Portfolio of instruments can be serialised to disk via portfolio.to_csv() however the output csv file cannot then be reloaded via Portfolio.from_csv(). Effectively the to/from csv feature is useless as we cannot round trip portfolios of instruments.

To Reproduce

from gs_quant.common import PayReceive, Currency
from gs_quant.instrument import IRSwap
from gs_quant.markets.portfolio import Portfolio

def build_portfolio() -> Portfolio:
    p = Portfolio()
    for ccy in (Currency.EUR, Currency.GBP, Currency.USD, Currency.AUD):
        for tenor in (f'{x}Y' for x in range(1, 31)):
            p.append(
                IRSwap(PayReceive.Receive,
                       notional_amount=100_000_00,
                       notional_currency=ccy,
                       fixed_rate='atm',
                       name=f'{ccy}_{tenor}'
                       )
            )
    return p

def main():
    file_name = r'c:\temp\portfolio.csv'
    build_portfolio().to_csv(file_name)
    load_portfolio = Portfolio.from_csv(file_name)
    assert load_portfolio == build_portfolio()

if __name__ == '__main__':
    main()

Expected behavior To be able to load a portfolio of trades from csv. The above code should run to completion.

Screenshots image.

Systems setup:

Additional context The issue is one of data sanitation. Before saving to csv all the Enums should be replaced by their .values (as when serialising to JSON or MSGPack) this way when read back in the get_enum_from() code will be able to convert a 'Swap' into an AssetType.Swap - at the moment it fails to convert an 'AssetType.Swap'

Instead of saving this: ,asset_class,fee,fixed_rate,name,notional_amount,notional_currency,pay_or_receive,type 0,AssetClass.Rates,0.0,atm,Currency.EUR_1Y,10000000,Currency.EUR,PayReceive.Receive,AssetType.Swap 1,AssetClass.Rates,0.0,atm,Currency.EUR_2Y,10000000,Currency.EUR,PayReceive.Receive,AssetType.Swap 2,AssetClass.Rates,0.0,atm,Currency.EUR_3Y,10000000,Currency.EUR,PayReceive.Receive,AssetType.Swap 3,AssetClass.Rates,0.0,atm,Currency.EUR_4Y,10000000,Currency.EUR,PayReceive.Receive,AssetType.Swap

You would save: ,asset_class,fee,fixed_rate,name,notional_amount,notional_currency,pay_or_receive,type 0,Rates,0.0,atm,Currency.EUR_1Y,10000000,EUR,Receive,Swap 1,Rates,0.0,atm,Currency.EUR_2Y,10000000,EUR,Receive,Swap 2,Rates,0.0,atm,Currency.EUR_3Y,10000000,EUR,Receive,Swap 3,Rates,0.0,atm,Currency.EUR_4Y,10000000,EUR,Receive,Swap

One could make the argument that all the Enums you declare should have a repr of self.value rather than just have that in the parent class EnumBase - as somehow the MRO is getting messed around and Currency.EUR has a MRO of (<enum 'Currency'>, <class 'gs_quant.base.EnumBase'>, <enum 'Enum'>, <class 'object'>) and so the repr of Currency.EUR is 'Currency.EUR' rather than 'EUR' - which is what I assume was the intention of overriding the _repr on EnumBase?