Practice 2: Creating Token Price Model
We will be creating the below model:
from credmark.cmf.model import Model
from credmark.cmf.types import Price, Some, Token
from models.dtos.price import DexPoolAggregationInput, PoolPriceInfo
@Model.describe(slug='contrib.example-token-price',
version='1.0',
display_name='Token Price - weighted by liquidity',
description='The Current Credmark Supported Price Algorithm',
developer='Credmark',
input=Token,
output=Price)
class TokenPriceModel(Model):
"""
Return token's price
"""
WEIGHT_POWER = 4
def run(self, input: Token) -> Price:
all_pool_infos = self.context.run_model('price.dex-pool',
input=input,
return_type=Some[PoolPriceInfo])
# DexPoolAggregationInput is a DTO that embodies
# - Token
# - Pricing setup, and
# - pool information
pool_aggregator_input = DexPoolAggregationInput(
**all_pool_infos.dict(),
**input.dict(),
weight_power=self.WEIGHT_POWER,
debug=False)
return self.context.run_model('price.pool-aggregator',
input=pool_aggregator_input,
return_type=Price)
Create a new class as a subclass of
credmark.cmf.model.Model
and then decorate it with the @Model.describe
decorator for the model meta data:@Model.describe(slug='contrib.example-token-price',
version='1.0',
display_name='Token Price - weighted by liquidity',
description='The Current Credmark Supported Price Algorithm',
developer='Credmark',
input=Token,
output=Price)
In
@Model.describe
, it starts with the unique reference to the model in field slug
, and version
. Bump the version up every time you update the model because cache will rely on this. display_name
, description
and developer
are descriptive fields, please furnish with explanation and helpful information for the model user.The
input
and output
fields are critically important fields, because they determines the I/O interface for the model. We use DTO or the general dict type (DTO preferred). Here we specify Token as input and Price as output, both of them are pre-defined DTOs in Cmf. Below are the excerpt of how Price and Token are defined.class Price(DTO):
price: float = DTOField(0.0, description='Value of one Token')
src: Union[str, None] = DTOField(None, description='Source')
class Token(Contract):
"""
Token represents a fungible Token that conforms to ERC20
standards
"""
...
The definition of the model class is shown below.
class TokenPriceModel(Model)
The model class
TokenPriceModel
needs to be inherited from the Model class. Here we also inherit from another class PriceWeight
as it needs some of its defined methods. The model class can have its own methods and variable but it must implement its own method of run(self, input)
, which takes input data (a Token) and returns a result Price.Access utilities with
self.context
, log using self.logger
. The type hints for the input argument and output of the run method need to be the same as specified in the @Model.describe
decorator.def run(self, input: Token) -> Price:
self.context.run_model
method invoke the call to the other model with input and transform the output to a DTO specified. Here, we call price.dex-pool
to return DTO type Some[PoolPriceInfo]
. Some
is a DTO type that can contain some objects of the same type. It's the generic form for list. all_pool_infos = self.context.run_model('price.dex-pool',
input=input,
return_type=Some[PoolPriceInfo])
Next, we prepare
PoolPriceAggregator
DTO which packages the token to be priced, pricing parameters and the pools' information for the final model call to price.pool-aggregator
. # DexPoolAggregationInput is a DTO that embodies
# - token
# - pricing setup, and
# - pool information
pool_aggregator_input = DexPoolAggregationInput(
**all_pool_infos.dict(),
**input.dict(),
weight_power=self.WEIGHT_POWER,
debug=False)
In the end of the model, we use
self.context.run_model
to call model price.pool-aggregator
with the data preprared the input and returns the Price
as the output. return self.context.run_model('price.pool-aggregator',
input=pool_aggregator_input,
return_type=Price)
Finally, we launch the model. The command-line with the modeling tool with the
run
command is:credmark-dev run contrib.example-token-price -i \
'{"address":"0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852"}' -j
And the output will look like this:
2022-10-25 14:47:25,361 - credmark.cmf.engine.context - INFO - Using latest block number 15823165
{
"slug": "contrib.example-token-price",
"version": "1.0",
"chainId": 1,
"blockNumber": 15823165,
"output": {
"price": 45268462101.77488,
"src": "uniswap-v2|Non-zero:1|Zero:0"
},
"dependencies": {
"dex.primary-tokens": {
"0.1": 15
},
"contract.metadata": {
"1.0": 1
},
"uniswap-v2.get-pools": {
"1.7": 5
},
"uniswap-v2.get-pool-price-info": {
"1.11": 10
},
"uniswap-v2.get-pool-info-token-price": {
"1.12": 5
},
"uniswap-v2.get-weighted-price": {
"1.7": 4
},
"sushiswap.get-v2-factory": {
"1.0": 1
},
"sushiswap.get-pools": {
"1.5": 1
},
"sushiswap.get-pool-info-token-price": {
"1.10": 1
},
"uniswap-v3.get-pools": {
"1.5": 1
},
"uniswap-v3.get-pool-info-token-price": {
"1.15": 1
},
"price.dex-pool": {
"0.4": 1
},
"price.pool-aggregator": {
"1.8": 1
},
"contrib.example-token-price": {
"1.0": 1
}
}
}
For all patient reader, the model source is available at https://github.com/credmark/credmark-models-py/blob/main/models/contrib/kunlun/example_token_price.py