uqfoundation/dill

Error on loading file : AttributeError: 'Config' object has no attribute '_readonly_'

Closed this issue · 3 comments

Can not pickle vectorbt object

import vectorbt as vbt

data = vbt.BinanceData.download(
    ['BTCUSDT', 'ETHUSDT'], 
    start='2020-01-01',
    end='2022-01-01',
    interval='1h'
)

import dill

with open('indication_data.pkl', 'wb') as f:
    dill.dump(data, f)

with open('indication_data.pkl', 'rb') as f:
    data = dill.load(f)

Error:


File c:\Users\ufo\anaconda3\lib\site-packages\dill\_dill.py:313, in load(file, ignore, **kwds)
    307 def load(file, ignore=None, **kwds):
    308     """
    309     Unpickle an object from a file.
    310 
    311     See :func:`loads` for keyword arguments.
    312     """
--> 313     return Unpickler(file, ignore=ignore, **kwds).load()

File c:\Users\ufo\anaconda3\lib\site-packages\dill\_dill.py:525, in Unpickler.load(self)
    524 def load(self): #NOTE: if settings change, need to update attributes
--> 525     obj = StockUnpickler.load(self)
    526     if type(obj).__module__ == getattr(_main_module, '__name__', '__main__'):
    527         if not self._ignore:
    528             # point obj class to main

File c:\Users\ufo\anaconda3\lib\site-packages\vectorbt\utils\config.py:507, in Config.__setitem__(self, k, v, force)
    506 def __setitem__(self, k: str, v: tp.Any, force: bool = False) -> None:
--> 507     if not force and self.readonly_:
    508         raise TypeError("Config is read-only")
    509     if not force and self.frozen_keys_:

File c:\Users\ufo\anaconda3\lib\site-packages\vectorbt\utils\config.py:485, in Config.readonly_(self)
    482 @property
    483 def readonly_(self) -> bool:
    484     """Whether to deny any updates to the config."""
--> 485     return self._readonly_

AttributeError: 'Config' object has no attribute '_readonly_'

I can work with you to figure out why it's failing to pickle... however, this is an issue for the vectorbt developers. The traceback looks like it's being kicked to pickle. First step to figure out what the issue is would be to turn on tracing (i.e. dill.detect.trace(True)) and then run it again.

I realize this issue is fairly old now but the reason is because Vectorbt has a superclass called Pickleable, which many of its objects are a subclass of. In this case you should use the Pickleable class' methods directly on the object rather than providing the object to pickle.dump(). Aside from the syntax, the behavior should be the same:

data = vbt.YFData.download(
    ['SPY', 'QQQ'], 
    start='2020-01-01',
    end='2022-01-01',
    interval='1d'
)

data.save('pickled_data')  # creates a file called pickled_data in the current directory

print(type(data))  # Output: <class 'vectorbt.data.custom.YFData'>

Then when you need the saved data again, provide the full object type or the shortened version:

unpickled_data = vbt.data.custom.YFData.load('pickled_data')
# or
unpickled_data = vbt.YFData.load('pickled_data')

If you only need a reference to the pickled object then you should do:

pickled_data = data.dumps()
...
unpickled_data = vbt.YFData.loads(pickled_data)

However, I should mention that this data object is basically just a dictionary. You likely want to use the get method:

data = vbt.YFData.download(
    ['SPY', 'QQQ'], 
    start='2020-01-01',
    end='2022-01-01',
    interval='1d'
).get('Open')

This will return a pandas DataFrame which can be pickled directly through the pickle/dill module or with data.to_pickle('pickled_data').

I mostly explained this since it's necessary to use the Pickleable methods for saving the vbt.Portfolio and other valuable objects from backtesting.

I'm going to close this, as it would seem the answer is vectorbt expects to use it's own pickler.