Source code for tradeexecutor.state.sync
""""Store information about caught up chain state.
- Treasury understanding is needed in order to reflect on-chain balance changes to the strategy execution
- Most treasury changes are deposits and redemptions
- Interest rate events also change on-chain treasury balances
- See :py:mod:`tradeexecutor.strategy.sync_model` how to on-chain treasuty
"""
import datetime
from dataclasses import dataclass, field
from typing import Optional, List, Iterable, Dict
from dataclasses_json import dataclass_json
from tradeexecutor.state.interest_distribution import InterestDistributionOperation
from tradeexecutor.state.types import USDollarAmount, BlockNumber
from tradingstrategy.chain import ChainId
from tradeexecutor.state.balance_update import BalanceUpdate, BalanceUpdateCause, BalanceUpdatePositionType
[docs]@dataclass_json
@dataclass
class Deployment:
"""Information for the strategy deployment.
- Capture information about the vault deployment in the strategy's persistent state
- This information can be later used to look up information (e.g deposit transactions)
- This information can be later used to look up verify data
"""
#: Which chain we are deployed
chain_id: Optional[ChainId] = None
#: Vault smart contract address
#:
#: For hot wallet execution, the address of the hot wallet
address: Optional[str] = None
#: When the vault was deployed
#:
#: Not available for hot wallet based strategies
block_number: Optional[int] = None
#: When the vault was deployed
#:
#: Not available for hot wallet based strategies
tx_hash: Optional[str] = None
#: UTC block timestamp of the vault deployment tx
#:
#: Not available for hot wallet based strategies
block_mined_at: Optional[datetime.datetime] = None
#: Vault name
#:
#: Enzyme vault name - same as vault toke name
vault_token_name: Optional[str] = None
#: Vault token symbol
#:
#: Enzyme vault name - same as vault toke name
vault_token_symbol: Optional[str] = None
#: When the initialisation was complete
#:
initialised_at: Optional[datetime.datetime] = None
def __repr__(self):
return f"<Deployment chain:{self.chain_id.name} address:{self.address} name:{self.vault_token_name} token:{self.vault_token_symbol}>"
[docs]@dataclass_json
@dataclass
class BalanceEventRef:
"""Register the balance event in the treasury model.
Balance updates can happen for
- Treasury
- Open trading positions
We maintain a list of balance update references across all positions using :py:class:`BalanceEventRef`.
This allows us quickly to calculate net inflow/outflow.
"""
#: Balance event id we are referring to
balance_event_id: int
#: When this update was made.
#:
#: Strategy cycle timestamp when the deposit/redemption was included
#: in the strategy treasury.
#:
#: It might be outside the cycle frequency if treasuries were processed
#: in a cron job outside the cycle for slow moving strategies.
#:
strategy_cycle_included_at: Optional[datetime.datetime]
#: Cause of the event
cause: BalanceUpdateCause
#: Reserve currency or underlying position
position_type: BalanceUpdatePositionType
#: Which trading positions were affected
position_id: Optional[int]
#: How much this deposit/redemption was worth
#:
#: Used for deposit/redemption inflow/outflow calculation.
#: This is the asset value from our internal price keeping at the time of the event.
#:
usd_value: Optional[USDollarAmount]
[docs]@dataclass_json
@dataclass
class Treasury:
"""State of syncind deposits and redemptions from the chain.
"""
#: Wall clock time. timestamp for which we run the last sync
#:
#: Wall clock time, at the beginning on the sync cycle.
last_updated_at: Optional[datetime.datetime] = None
#: The strategy cycle timestamp for which we run the last sync
#:
#: Wall clock time, at the beginning on the sync cycle.
last_cycle_at: Optional[datetime.datetime] = None
#: What is the last processed block for deposit
#:
#: 0 = not scanned yet
last_block_scanned: Optional[int] = None
#: List of refernces to all balance update events.
#:
#: The actual balance update content is stored on the position itself.
balance_update_refs: List[BalanceEventRef] = field(default_factory=list)
def __repr__(self):
return f"<Treasury updated:{self.last_updated_at} cycle:{self.last_cycle_at} block scanned:{self.last_block_scanned or 0:,} refs:{len(self.balance_update_refs)}>"
[docs]@dataclass_json
@dataclass
class Accounting:
"""State of accounting corrections.
"""
#: Wall clock time. timestamp for which we run the last sync
#:
#: Wall clock time, at the beginning on the sync cycle.
last_updated_at: Optional[datetime.datetime] = None
#: What is the last processed block for deposit
#:
#: 0 = not scanned yet
last_block_scanned: Optional[int] = None
#: List of refernces to all balance update events.
#:
#: The actual balance update content is stored on the position itself.
balance_update_refs: List[BalanceEventRef] = field(default_factory=list)
[docs]@dataclass_json
@dataclass(slots=True)
class InterestSync:
"""Track the interest sync for on-chain rebase tokens.
"""
#: When did we perform the last interest sync all all assets
#:
last_sync_at: datetime.datetime | None = None
#: Block number when we synced the portfolio
#:
#: Backtesting does not use block numbers and has this always set to ``None``.
#:
last_sync_block: BlockNumber | None = None
#: Last operation we did.
#:
#: Data is not needed. Stored only for diagnostics purposes.
#: Always overwritten in the next sync.
#:
last_distribution: InterestDistributionOperation | None = None
[docs]@dataclass_json
@dataclass
class Sync:
"""On-chain sync state.
- Store persistent information about the vault on transactions we have synced,
so that the strategy knows its available capital
- Updated before the strategy execution step
"""
deployment: Deployment = field(default_factory=Deployment)
treasury: Treasury = field(default_factory=Treasury)
accounting: Accounting = field(default_factory=Accounting)
interest: InterestSync = field(default_factory=InterestSync)
[docs] def is_initialised(self) -> bool:
"""Have we scanned the initial deployment event for the sync model."""
return self.deployment.block_number is not None