Code
The code part of the simulation is the most complex to configure but is also the beating hart of the simulator. Please reference every entry below and write actions that you would like the simulator and agents to take during the simulator.
The specific variable for the code
sections are always a string, this can be
the code that you specify right in the configuration file as a string. But you
can also define a file link using file://
as leading string and then specify
the file. The file can be specified in a folder either by giving the full path
by leading the path with a /
or the relative path by leading it with .
but
also from your home directory by leading it with ~
.
SDK
For ease of use an SDK has been published with the classes of each input. When you're writing the code in a file you can easily import this sdk and use it to get easier type hinting.
To install the SDK please follow these steps.
- Add this to
~/.pypirc
[distutils]
index-servers =
almanak-py
[almanak-py]
repository: https://europe-west4-python.pkg.dev/almanak-production/almanak-py/
- Make sure this is in the pip.conf file of which the location you can fine
here. (If the
file is in the wrong location it won't work, for me it's
~/.config/pip/pip.conf
)
[global]
extra-index-url = https://europe-west4-python.pkg.dev/almanak-production/almanak-py/simple/
- If this is set you can do
pip install almanak-simulations-sdk
Order of Code Execution
User code is executed in the following order:
- Environment initialization
- Agent initialization
- Environment pre-step
- Agent pre-step
- Agent feature vector
- Agent step
- Agent post-step
- Environment post-step
- Agent teardown
- Environment teardown
- Reward function
Note that Steps 1 and 2 are executed once per simulation before any steps are made, Steps 3 through 8 are executed on each step, and Steps 9 though 11 are executed once per simulation after all steps have completed.
This means, for instance, that environment initialization code would not be able to reference any particular agents, as the agents would not yet have been initialized at this point. Please keep in mind the order of operations when defining each code block.
Levels of code
There are multiple levels of code. Some code blocks can be overwritten by more specific levels, this gives the engineer who wants to run a simulation very fine-grained control of what each agent performs without having to have duplicate code in the simulation source files.
The top of the following level is the most specific and will always be taken first, if none is set it will go down the list of these options until it finds one that is set and set the code for the agent to that.
- Agent
- Agent Class
- General Code (this document)
So if there's code set directly to an agent it will always have priority over an
agent class. If no agent code has been specified it will look at the agent class
that the agent uses and if it has code, if it does the agent will use that. If
it does not then it will use the general code set in the code
field in the
configuration file.
Wrapper Classes
Wrapper classes provide code blocks with access to some internal components of the simulator itself. These include the ability to define new variables, read current and historical price data, and access the EVM client to invoke functions at that level.
There are two wrapper classes: EnvironmentWrapper
and AgentWrapper
.
EnvironmentWrapper
provides access to environment-level data, and any custom
variables defined at this level will persist across all agents. AgentWrapper
,
on the other hand, provides agent-specific data based on the current agent
taking a step, and any variables defined at this level will only be visible to
that particular agent.
Environment Wrapper
This class is a wrapper for accessing particular functions and values in the
GenericEnvironment
class without directly surfacing all functions and inner
variables for the class itself.
__init__
This function initializes an instance of the EnvironmentWrapper
class. It
copies specific attributes from the provided _environment
object to the newly
created EnvironmentWrapper
instance.
Parameters
_environment
: TheGenericEnvironment
instance to be wrapped.
Returns
None
.
add_variable
This function allows you to add a custom variable to the environment that you can read from at a later point.
Parameters
*args, **kwargs
: The parameters are passed directly to the underlying_environment.add_variable()
method.
Returns
None
.
get_variable
This function retrieves a previously set variable from the environment.
Parameters
*args, **kwargs
: The parameters are passed directly to the underlying_environment.get_variable()
method.
Returns
The value of the variable. This can be of any type, depending on what was previously set.
set_variable
This function allows you to set a custom variable that you have added before to a different value.
Parameters
*args, **kwargs
: The parameters are passed directly to the underlying_environment.set_variable()
method.
Returns
None
.
get_client
This function returns the client that can be found in the environment.
Parameters
None.
Returns
ProjectClient
: The client that is used to perform actions. If no client
exists, returns None
.
get_current_step
Retrieve the current step number in the simulation.
Parameters
None.
Returns
int
: The current step number.
get_all_prices
Retrieve the full price series. This can either be historical or simulated
price data based on the historical
flag. If simulated data is retrieved,
prices up to the current step are returned, preventing any agent lookahead.
Parameters
None.
Returns
Dict
: The key of the dict is the token and the value is the price list.
get_current_prices
This function retrieves the current price for the current step in the environment.
Parameters
None.
Returns
Dict
: The key of the dict is the token symbol and the value is the price.
get_current_token_price
This function retrieves the current price for the current step for a specific token.
Parameters
token (str)
: The token symbol to retrieve the current price for.
Returns
float
: The current price for the token.
get_current_ohlc_prices
This function retrieves the current OHLC (open, high, low, and close) prices for the current step in the environment.
Parameters
None.
Returns
Dict
: The key of the dict is the token symbol and the value is the OHLC
price.
get_ohlc_prices
This function retrieves the current OHLC prices for the given index in the environment.
Parameters
index (int)
: The index corresponds to the environment step.
Returns
Dict
: The key of the dict is the token symbol and the value is the OHLC
price.
get_historical_prices
This function retrieves historical prices for each asset.
Parameters
lookback (int)
: How many steps you want to look back to retrieve the historical price.
Returns
Dict
: The key of the dict is the token symbol and the value is the closing
price.
get_historical_price_slice
This function retrieves an array of historical prices for a single asset.
Parameters
token (str)
: The token symbol to retrieve the historical price data for.lookback (int)
: How many steps you want to look back to retrieve the historical prices. A value of 0 corresponds to the previous price.
Returns
List
: The array of past prices.
get_volatilities
This function retrieves the volatility for each asset as modeled by the price simulator when generating the prices.
Parameters
lookback (int)
: How many steps you want to look back to retrieve the
volatility value.
get_price_slice_std
For a given token
, take the last lookback
prices from the current_step,
and compute the sample standard deviation of that price slice.
Parameters
token (str)
: The token symbol to retrieve the volatility of the price data for.lookback (int)
: How many prices backwards from the current step to collect.
Returns
float
: The value of the sample standard deviation of the price slice.
get_environment_id
This function retrieves the ID of the environment.
Parameters
None.
Returns
int
: The ID of the environment.
get_observer
This function retrieves the observer. Note that this is not currently used and will be replaced by the metric collector.
Parameters
None.
Returns
Observer
: The observer object which can be used to set or read information.
get_parameters
This function retrieves the parameters that were set.
Parameters
None.
Returns
Dict
: Key-value dict of all the parameters.
get_params
This function retrieves the value of a single parameter.
Parameters
None.
Returns
Any
: The value of the parameter.
get_price_discrepancy
This function retrieves the price discrepancy for a given token.
Parameters
token
: The token for which to retrieve the price discrepancy.
Returns
The price discrepancy of the given token.
set_price_discrepancy
This function sets the price discrepancy for a given token.
Parameters
token
: The token for which to set the price discrepancy.price_discrepancy
: The price discrepancy to set.
Returns
None
.
add_revenue
This function adds revenue to the environment.
Parameters
revenue
: The revenue to be added.
Returns
None
.
get_revenue
This function retrieves the current revenue of the environment.
Parameters
None.
Returns
The current revenue of the environment.
set_revenue
This function sets the revenue of the environment.
Parameters
revenue
: The revenue to be set.
Returns
None
.
get_state
This function retrieves the current state of the environment.
Parameters
None.
Returns
GenericState
: The current state of the environment, which includes the block
number and metadata for each token at that point in time.
set_state
This function sets the state of the environment.
Parameters
state: GenericState
: The state to be set.
Returns
None
.
get_swap_fee
This function retrieves the swap fee for a given token.
Parameters
token
: The token for which to retrieve the swap fee.
Returns
The swap fee of the given token.
set_swap_fee
This function sets the swap fee for a given token.
Parameters
token
: The token for which to set the swap fee.swap_fee
: The swap fee to set.
Returns
None
.
get_dsl_metamodel
This function retrieves the DSL metamodel if it exists.
Parameters
None.
Returns
The DSL metamodel if it exists, else None
.
get_folder_location
This function retrieves the folder location if it exists.
Parameters
None.
Returns
The folder location if it exists, else None
.
mine
Mine a block. This updates the base fee per gas for the next block based on the
defined gas strategy and then invokes the EVM mine()
function.
Parameters
None.
Returns
None.
get_price_granularity
If the granularity is specified in the price_simulator settings block
of the configuration.yaml, then this function will return the
value of that granularity parameter. It will return None
if
that parameter is not present.
Parameters
None
Returns
Union[int, None]
: a positive integer or None
Agent Wrapper
Serves as a wrapper for accessing particular functions and values in the Agent class without directly surfacing all functions and inner variables for the class itself.
add_variable
This function adds a custom variable to the agent instance.
Parameters
args
: Variable length argument list.kwargs
: Arbitrary keyword arguments.
Returns
None
.
get_variable
This function returns a previously set variable.
Parameters
args
: Variable length argument list.kwargs
: Arbitrary keyword arguments.
Returns
Any
: this can be anything that has been previously set.
set_variable
This function sets a custom variable that you have added before to a different value.
Parameters
args
: Variable length argument list.kwargs
: Arbitrary keyword arguments.
Returns
None
.
add_affected_token
This function adds an affected token. These affected tokens will have their internal states updated at the end of the agent step.
Parameters
token (string)
: The token symbol that has been affected.
Returns
None
.
get_address
This function retrieves the agent's address.
Parameters
None.
Returns
string
: The agent's address.
get_client
This function returns the EVM client that can be found in the environment that is attached to the agent.
Parameters
None.
Returns
ProjectClient
: The EVM client that is used to perform EVM actions. If no
client exists, returns None
.
get_latest_action
This function retrieves the latest action the agent performed.
Parameters
None.
Returns
Union[str, None]
: A string with a possible value. If no action has been
performed, returns None
.
set_latest_action
This function sets the latest action the agent performed.
Parameters
latest_action (string)
: The action that the agent performed.
Returns
None
.
get_step_list
Retrieve the list of steps that the agent will take for this environment step. Note that this list will be cleared after each environment step. Further note that this list only affects the current agent and is not representative of the steps that will be taken by other agents for this environment step.
Parameters
None.
Returns
List[AgentStep]
: The list of steps for the agent to take.
set_step_list
Given a list of AgentStep
instances, set this as the list to be stepped
over for the current agent's environment step. This should be set in the
agent pre-step logic.
Parameters
step_list (List[AgentStep])
: The list of steps to assign to the agent.
Returns
None
.
get_wallet
This function retrieves the agent's wallet.
Parameters
None.
Returns
Wallet
: The wallet, which contains information about the holdings of the
agent.
get_settings
This function retrieves the agent's settings.
Parameters
None.
Returns
dict
: The agent's settings.
Code Blocks
You can find a description for every code block below. Each code block can be
provided in the code
section of the configuration but do take into account
that these sections can be overwritten by Agent Class
functions and Agent functions.
environmentInitialization
Goal
The goal of the environment initialization is to call any functions that you
would like to setup before running the simulation. This is not a requirement but
there might some initialization steps that you would like to do before your
simulation runs.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
agentInitialization
Goal
Similar to the environmentInitialization
you can initialize an agent here by
calling some functions to get it to anitial state.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| AgentWrapper
|
| b
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
environmentPreStep
Goal
Before performing a step for all the agents you are given the chance to perform
functions before all the agents will be able to do something.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
agentPreStep
Goal
The agent pre-step will be run for every agent separately and provides the agent
class that will about to perform a step.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| AgentWrapper
|
| b
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
agentFeatureVector
Goal
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| agent
| AgentWrapper
|
| step
| AgentStep
|
| model_alias
| str
|
| environment
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
agentStep
Goal
Perform the agent step. This function is very important, you will have to write
protocol specific logic to performing a certain step using the inputs that are
provided.
Before the agentStep code executes, the simulator will call each model that is
specified within the agents models
setting. The prediction from each model
is collected into a dictionary in which the key is the model alias
and the value is the output of the model prediction.
The model prediction dictionary can be accessed via step.get_model_output()
.
step
is theAgentStep
object that the agent is stepping on, including any custom attributes that have been defined by the user in the agent pre-step code.
Function inputs
input name | type |
---|---|
a | AgentWrapper |
b | `token: Any |
c | action: float |
d | `quantity: Any |
e | EnvironmentWrapper |
metric_collector | MetricCollectorWrapper |
agentPostStep
Goal
After the step has been performed is a good moment to do some recalculations or
metric reporting.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| AgentWrapper
|
| b
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
environmentPostStep
Goal
Similar to the agentPostStep
this is run after all the agents have finished
performing steps. Which could be a good moment to perform an action or calculate
and report metrics.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
agentTeardown
Goal
After all steps have been performed the agent will be torn down. Before all data
is erased you can do a final result or metric calculation and report those here.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| AgentWrapper
|
| b
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
environmentTeardown
Goal
Similar to the agentTeardown
before the environment is destroyed and all data
deleted you can do a final metric calculation or action here.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| a
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
reward
Goal
Using the environment class please calculate what the reward value of this
specific simulation is. This can be as easy as calling
environment.get_reward()
and reporting that, but could be very elaborate based
on the protocol that you're trying to simulate.
Function inputs
| input name
| type
|
| ------------------ | ------------------------ |
| environment
| EnvironmentWrapper
|
| metric_collector
| MetricCollectorWrapper
|
environmentMisc
Goal
The Misc
code block enables you to write functions for the specific class that
can be called from the other functions. This makes the software writing process
easier. This works great for making re-usable functions that you plan on using
multiple times in your code from the environment class. It is made available
directly in your code. So if you created a function def add_one(num: int):
this can be called by calling add_one()
directly.
agentMisc
Goal
The Misc
code block enables you to write functions for the specific class that
can be called from the other functions. This works exactly the same as the environmentMisc
.
Example
code:
environmentInitialization: |
def environment_initialization(environment, metric_collector):
determine_swapping_fee(environment)
environment_initialization(a, metric_collector)
agentInitialization: "file://code/agentInitialization.py"
environmentPreStep: "file://code/environmentPreStep.py"
agentPreStep: "file://code/agentPreStep.py"
agentFeatureVector: "file://code/agentFeatureVector.py"
agentStep: "file://code/agentStep.py"
agentPostStep: "file://code/agentPostStep.py"
environmentPostStep: "file://code/environmentPostStep.py"
agentTeardown: "file://code/agentTeardown.py"
environmentTeardown: "file://code/environmentTeardown.py"
reward: "file://code/reward.py"
environmentMisc: "file://code/environmentMisc.py"
agentMisc: "file://code/agentMisc.py"
An example of a file:
import collections
from almanak.simulations.agent import AgentWrapper
from almanak.simulations.environment import EnvironmentWrapper
from almanak.simulations.metriccollector import MetricCollectorWrapper
def agent_initialization(agent: AgentWrapper, environment: EnvironmentWrapper, metric_collector: MetricCollectorWrapper):
agent.set_variable('withdrawal_request_ids_by_token', collections.defaultdict(list))
# pyright: reportUndefinedVariable=false
agent_initialization(a, b, metric_collector)
For the full example use the CLI with the generate
command, this
has all code files included.