Source code for tradeexecutor.ethereum.token
"""ERC-20 token data helpers."""
from web3 import Web3
from eth_defi.token import TokenDetails, fetch_erc20_details
from tradeexecutor.state.identifier import AssetIdentifier
from tradeexecutor.state.portfolio import Portfolio
from tradeexecutor.state.position import TradingPosition
from tradeexecutor.state.types import JSONHexAddress
from tradingstrategy.pair import PandasPairUniverse
class NonSpotPosition(Exception):
"""Portfolio has non-spot position when spot-only is supported"""
[docs]def fetch_token_as_asset(web3: Web3, contract_address: str) -> AssetIdentifier:
"""Get ERC-20 token details suitable for persistent stroage.
:param contract_address:
Address of an ERC-20 contract
:return:
Asset identifier that can be use with persistent storage.
"""
token = fetch_erc20_details(web3, contract_address)
return translate_token_details(token)
[docs]def translate_token_details(token: TokenDetails) -> AssetIdentifier:
"""Translate on-chain fetched ERC-20 details to the persistent format.
:param token:
On-chain token data
:return:
Persistent asset identifier
"""
web3 = token.contract.w3
return AssetIdentifier(
chain_id=web3.eth.chain_id,
address=token.address,
token_symbol=token.symbol,
decimals=token.decimals,
internal_id=None,
info_url=None,
)
[docs]def create_spot_token_map_existing_positions(
portfolio: Portfolio,
raise_on_unsupported=True,
) -> dict[JSONHexAddress, TradingPosition]:
"""Create a map of spot ERC-20 tokens in our portfolio.
:return:
ERC-20 address -> position map for all open/frozen spot positions.
"""
position_map = {}
for p in portfolio.get_open_and_frozen_positions():
if not p.is_spot():
if raise_on_unsupported:
raise NonSpotPosition(f"Not ERC-20 spot position: {p}")
else:
continue
position_map[p.pair.base.address] = p
return position_map
[docs]def create_spot_token_map_potential_positions(
pair_universe: PandasPairUniverse,
portfolio: Portfolio,
raise_on_unsupported=True,
) -> dict[AssetIdentifier, TradingPosition | None]:
"""Create a map of spot ERC-20 tokens in our portfolio.
:return:
ERC-20 address -> position map for all open/frozen spot positions and potential positions.
IF there is not yet existing position, return None.
"""
position_map = create_spot_token_map_existing_positions(portfolio, raise_on_unsupported)
# Include tokens for which we do not have a position mapped yet
for pair in pair_universe.iterate_pairs():
if pair.base_token_address not in position_map:
position_map[pair.base_token_address] = None
return position_map