Source code for tradeexecutor.state.size_risk
"""Trade size risk estimation.
- See :py:class:`SizeRisk`
"""
import datetime
import enum
from dataclasses import dataclass
from decimal import Decimal
from types import NoneType
from dataclasses_json import dataclass_json
from tradeexecutor.state.identifier import TradingPairIdentifier
from tradeexecutor.state.types import BlockNumber, TokenAmount, USDollarAmount, USDollarPrice
[docs]class SizingType(enum.Enum):
"""What kind of size risk the result is for.
- See :py:class:`SizeRisk`
"""
#: Individual trade, buy
buy = "buy"
#: Individual trade, sell
sell = "sell"
#: Position size
hold = "hold"
[docs]@dataclass_json
@dataclass(slots=True)
class SizeRisk:
"""Result of a price impact estimation during decide_trades().
- Used by py:class:`~tradeexecutor.strategy.trade_sizer.TradeSizer`
to return the estimations of the safe trade sizes (not too much price impact, not too much liquidity risk)
- Capture and save data about the price impact, so we can diagnose this later.
Not just the result, but the variables that lead to the result.
- Allows us to use this to cap the max position size
when we enter to a position.
.. note ::
A position size is different from a trade size,
as we may grow a larger position using a multiple trades to enter
See also
- :py:mod:`tradeexecutor.strategy.trade_pricing` for price impact / slippage analysis
- :py:func:`tradeexecutor.strategy.alpha_model.AlphaModel.normalise_weights` to have
portfolio specific weight % cap for each position
"""
#: For which timepoint this price impact estimation was made
#:
#: Can be set to None if not relevant.
#:
timestamp: datetime.datetime | None
pair: TradingPairIdentifier
#: Buy or sell
sizing_type: SizingType
#: Path of the trade
#: One trade can have multiple swaps if there is an intermediary pair.
path: list[TradingPairIdentifier]
#: Was this trade hitting the maximum cap.
#:
#: This means the trade size was reduced due to risk.
#:
capped: bool
#: Block number we used for onchain estimation
block_number: BlockNumber | None = None
#: Venue mid price per token
mid_price: USDollarPrice | None = None
#: Avg price per token when accepted size is filled
avg_price: USDollarPrice | None = None
#: How much we want to get
asked_quantity: TokenAmount | None = None
#: How much we want to get
asked_size: USDollarAmount | None = None
#: What was the capped size by the
accepted_quantity: TokenAmount | None = None
#: What was the capped size
accepted_size: USDollarAmount | None = None
#: Store various diagnostics data items ehre
#:
#: Each implementation can store e.g. percents
#:
diagnostics_data: dict | None = None
def __post_init__(self):
assert isinstance(self.timestamp, (datetime.datetime, NoneType)), f"Timestamp was {self.timestamp}"
if self.asked_quantity is not None:
assert isinstance(self.asked_quantity, Decimal)
if self.accepted_quantity is not None:
assert isinstance(self.accepted_quantity, Decimal)
if self.diagnostics_data is not None:
assert isinstance(self.diagnostics_data, dict)
# We can never allocate more than we ask
if self.asked_size:
assert self.accepted_size <= self.asked_size
# Numpy madness
if self.capped is not None:
assert type(self.capped) == bool, f"Expected bool, got {self.capped.__class__}"