Source code for tradeexecutor.state.interest_distribution
"""Distribute gained asset interest across positions holding those assets.
Data structures used in interest distribution.
"""
import datetime
from dataclasses import dataclass, field
from decimal import Decimal
from typing import Set, Dict, List
import logging
from dataclasses_json import dataclass_json
from tradeexecutor.state.identifier import AssetIdentifier, AssetWithTrackedValue, AssetFriendlyId
from tradeexecutor.state.loan import LoanSide
from tradeexecutor.state.position import TradingPosition
from tradeexecutor.state.types import USDollarPrice, Percent
from tradingstrategy.types import PrimaryKey
logger = logging.getLogger(__name__)
[docs]@dataclass_json
@dataclass(slots=True)
class InterestDistributionEntry:
"""Map interest distribution across different trading positions.
A helper class to help us to distribute accrued interest across different
related trading positions.
The lifetime of an instance is one sync_interests() run.
"""
#: Which side we are on
side: LoanSide
#: The trading position containing this asset
position: TradingPosition
#: Can be either loan.collateral or loan.borrower
#:
#: This is a pass-by-reference copy from Loan instance
#:
tracker: AssetWithTrackedValue
#: All weight are normalised to 0...1 based on loan token amount.
weight: Decimal = None
#: The updated price for the tracked asset
#:
#: To update US dollar based prices of interests,
#: we need to know any change in the asset prices.
#:
price: USDollarPrice = None
def __repr__(self):
return f"<InterestDistributionEntry {self.side.name} {self.position.pair.get_ticker()} {self.weight * 100}%>"
@property
def asset(self) -> AssetIdentifier:
"""Amount of tracked asset in tokens"""
return self.tracker.asset
@property
def quantity(self) -> Decimal:
"""Amount of tracked asset in tokens"""
return self.tracker.quantity
[docs]@dataclass_json
@dataclass(slots=True)
class AssetInterestData:
"""Per-asset data we track in interest calculations."""
#: Portfolio total quantity of interest bearing assets before the update.
#:
total: Decimal = field(default=Decimal(0))
#: Calculated the effective interest rate for this asset.
#:
#: What was the rate based on the operation duration and on-chain balance change.
#:
effective_rate: Percent = None
[docs]@dataclass_json
@dataclass(slots=True)
class InterestDistributionOperation:
"""One interest update batch we do."""
#: Starting period of time span for which we calculate the interest.
#:
#: Timestamp of the previously synced block.
#:
start: datetime.datetime
#: Ending period of time span for which we calculate the interest
#:
#: Timestamp of the the block end range
#:
end: datetime.datetime
#: All interest bearing assets we have across positions
assets: Set[AssetIdentifier]
#: Asset interest data entries keyed (chain id, address) tuples
#:
asset_interest_data: Dict[AssetFriendlyId, AssetInterestData]
#: All entries we need to update.
#:
#: One or two entries per position accruing interest.
#:
entries: List[InterestDistributionEntry]
effective_rate: Dict[int, Percent]
@property
def duration(self) -> datetime.timedelta:
"""The time span for which we updated the accrued interest."""
return self.end - self.start
def get_interest_data(self, asset: AssetIdentifier) -> AssetInterestData | None:
return self.asset_interest_data.get(asset.get_identifier())