from abc import ABC, ABCMeta
from typing import Any
from . import registry
from tensortrade.core.context import TradingContext, Context
from tensortrade.core.base import Identifiable
[docs]class InitContextMeta(ABCMeta):
"""Metaclass that executes `__init__` of instance in its core.
This class works with the `TradingContext` class to ensure the correct
data is being given to the instance created by a concrete class that has
subclassed `Component`.
"""
[docs] def __call__(cls, *args, **kwargs) -> 'InitContextMeta':
"""
Parameters
----------
args :
positional arguments to give constructor of subclass of `Component`
kwargs :
keyword arguments to give constructor of subclass of `Component`
Returns
-------
`Component`
An instance of a concrete class the subclasses `Component`
"""
context = TradingContext.get_context()
registered_name = registry.registry()[cls]
data = context.data.get(registered_name, {})
config = {**context.shared, **data}
instance = cls.__new__(cls, *args, **kwargs)
setattr(instance, 'context', Context(**config))
instance.__init__(*args, **kwargs)
return instance
[docs]class ContextualizedMixin(object):
"""A mixin that is to be mixed with any class that must function in a
contextual setting.
"""
@property
def context(self) -> Context:
"""Gets the `Context` the object is under.
Returns
-------
`Context`
The context the object is under.
"""
return self._context
@context.setter
def context(self, context: Context) -> None:
"""Sets the context for the object.
Parameters
----------
context : `Context`
The context to set for the object.
"""
self._context = context
[docs]class Component(ABC, ContextualizedMixin, Identifiable, metaclass=InitContextMeta):
"""The main class for setting up components to be used in the `TradingEnv`.
This class if responsible for providing a common way in which different
components of the library can be created. Specifically, it enables the
creation of components from a `TradingContext`. Therefore making the creation
of complex environments simpler where there are only a few things that
need to be changed from case to case.
Attributes
----------
registered_name : str
The name under which constructor arguments are to be given in a dictionary
and passed to a `TradingContext`.
"""
registered_name = None
[docs] def __init_subclass__(cls, **kwargs) -> None:
"""Constructs the concrete subclass of `Component`.
In constructing the subclass, the concrete subclass is also registered
into the project level registry.
Parameters
----------
kwargs : keyword arguments
The keyword arguments to be provided to the concrete subclass of `Component`
to create an instance.
"""
super().__init_subclass__(**kwargs)
if cls not in registry.registry():
registry.register(cls, cls.registered_name)
[docs] def default(self, key: str, value: Any, kwargs: dict = None) -> Any:
"""Resolves which defaults value to use for construction.
A concrete subclass will use this method to resolve which default value
it should use when creating an instance. The default value should go to
the value specified for the variable within the `TradingContext`. If that
one is not provided it will resolve to `value`.
Parameters
----------
key : str
The name of the attribute to be resolved for the class.
value : any
The `value` the attribute should be set to if not provided in the
`TradingContext`.
kwargs : dict, optional
The dictionary to search through for the value associated with `key`.
"""
if not kwargs:
return self.context.get(key, None) or value
return self.context.get(key, None) or kwargs.get(key, value)