ExperimentalAgents - abides-sim/abides GitHub Wiki
Create your own Experimental Agent
In this tutorial we will go through how to create an Experimental Agent in ABIDES. We will assume access to some reference configuration (here we will be using RMCS03). We will then go through the methods that need to be implemented for the agent to function and design a simple trading strategy. Then we can use tools provided in the ABIDES codebase to analyse the effectiveness of the strategy.
To keep the focus on the experimental agent design, we have provided a config housing the agent we'll create in this
tutorial in the file config/exp_agent_demo.py
(for more tips on config design check out this wiki page).
This config is a variant of our Reference Market Simulation Configuration config/rmsc03.py
with the option to house
one of two possible experimental agents, ExampleExperimentalAgentTemplate
and ExampleExperimentalAgent
, controlled
via a command-line switch. Both of these agents are found in the file agent/examples/ExampleExperimentAgent.py
.
The agent ExampleExperimentalAgentTemplate
is a simple agent to demonstrate the minimal set of methods an experimental
agent will need to implement to interact with the market. Indeed, this agent has no affect on the market as such. It
merely sits and receives market data at some subscription frequency (in the config we set this to every second but this
can be changed, along with the number of orderbook levels to subscribe to).
The agent also wakes up at a fixed frequency, but takes no
action. The agent ExampleExperimentalAgent
inherits from the ExampleExperimentalAgentTemplate
and implements a
simple mean-reversion strategy. We'll see that it takes a relatively small amount of code to implement a new strategy
due to the abstractions defined in the ABIDES API.
Let's take a look at the construction of the ExampleExperimentalAgent
, to implement your own strategy you can
copy/edit this class.
def __init__(self, *args, wake_freq, order_size, short_window, long_window, **kwargs):
super().__init__(*args, **kwargs)
self.wake_freq = wake_freq
self.order_size = order_size
self.short_window = short_window
self.long_window = long_window
self.mid_price_history = pd.DataFrame(columns=['mid_price'], index=pd.to_datetime([]))
In the constructor we need to initialise with the superclass *args
and **kwargs
to ensure that the agent has all
relevant attributes defined. We also define the following arguments unique to this strategy
wake_freq
: this will define how often the agent will trade.order_size
: this will be the size of market order placed.short_window
: this defines the length of time the trading algo considers "short-term".long_window
: this defines the length of time the trading algo considers "long-term".
We also initialise the attribute mid_price_history
: this variable will hold all of the mid-prices received from the
market data subscription. The agents strategy will be as follows:
Compute the "short-term" and "long-term" rolling averages of the observed market mid-price. If
short_term > long_term
we assume the mid-price will decrease, so sellorder_size
shares. Ifshort_term < long_term
we assume the mid-price will increase, so buyorder_size
shares.
We now define the agent's behaviour when it recieves a message from the exchange.
def receiveMessage(self, currentTime, msg):
super().receiveMessage(currentTime, msg) # receives subscription market data
self.mid_price_history = self.mid_price_history.append(
pd.Series({'mid_price': self.getCurrentMidPrice()}, name=currentTime))
self.mid_price_history.dropna(inplace=True)
We need to call the superclass' receiveMessage
method, which collects the subscription market data and processes meassages
from the exchange such as when the market opens and closes. We also append the current mid price to the agent's price history
using the getCurrentMidPrice
method, defined as follows.
def getCurrentMidPrice(self):
try:
best_bid = self.current_bids[0][0]
best_ask = self.current_asks[0][0]
return round((best_ask + best_bid) / 2)
except (TypeError, IndexError):
return None
The self.current_bids
and self.current_asks
attributes are populated with the orderbook subscription data by the receiveMessage
method.
They are each a list, where each element is a tuple (price, quantity)
.
We now need to define the wakeup behaviour of the agent. First we overload the getWakeFrequency
method to set the next
wakeup as self.wake_freq
. Note that getWakeFrequency
can return a next wakeup using randomness or some deterministic formula,
it need not be a constant. Here for simplicity we set a constant wakeup.
def getWakeFrequency(self):
return pd.Timedelta(self.wake_freq)
We also need to override the wakeup
method, implementing the mean-reversion strategy.
def wakeup(self, currentTime):
super().wakeup(currentTime)
short_moving_avg, long_moving_avg = self.computeMidPriceMovingAverages()
if short_moving_avg is not None and long_moving_avg is not None:
if short_moving_avg > long_moving_avg:
self.placeMarketOrder(self.order_size, 0)
elif short_moving_avg < long_moving_avg:
self.placeMarketOrder(self.order_size, 1)
First we must call the superclass wakeup
method. We then compute moving averages with the computeMidPriceMovingAverages
method (defined below). Notice we then implement the placeMarketOrder
method. This method is defined in ExampleExperimentalAgentTemplate
along with placeLimitOrder
and cancelAllOrders
, which can be used for other user-defined strategies.
def computeMidPriceMovingAverages(self):
try:
short_moving_avg = self.mid_price_history.rolling(self.short_window).mean().iloc[-1]['mid_price']
long_moving_avg = self.mid_price_history.rolling(self.long_window).mean().iloc[-1]['mid_price']
return short_moving_avg, long_moving_avg
except IndexError:
return None, None
This method computes the short and long term moving average mid-prices from the market data using a pandas
function.
That is all the code needed to define the mean-reversion agent, we can test how profitable this family of strategies is for a number of different parameter values. Let us try two settings:
- (I) Short-term window of 1 second, long-term window of 30 seconds.
- (II) Short-term window of 2 minutes, long-term window of 5 minutes.
We can run each of these settings for 30 seeds like so:
Note that you'll need the free command-line utility GNU parallel to run the following commands.
NUM_JOBS=48 # number of jobs to run in parallel, may need to reduce to satisfy computational constraints
for seed in $(seq 100 130); do
sem -j${NUM_JOBS} --line-buffer python -u abides.py -c exp_agent_demo -t ABM -d 20200603 -s ${seed} \
-l experimental_agent_demo_short_1s_long_30s_${seed} -e --ea-short-window '1s' --ea-long-window '30s'
done
for seed in $(seq 100 130); do
sem -j${NUM_JOBS} --line-buffer python -u abides.py -c exp_agent_demo -t ABM -d 20200603 -s ${seed} \
-l experimental_agent_demo_short_2min_long_5min_${seed} -e --ea-short-window '2min' --ea-long-window '5min'
done
Once these experiments are complete we can compare profitability. For config I:
python -u cli/read_agent_logs.py log/experimental_agent_demo_short_1s_long_30s_*
Return Surplus
AgentType
ExampleExperimentalAgent -992551.806452 -2.696557e+08
and for config II:
python -u cli/read_agent_logs.py log/experimental_agent_demo_short_2min_long_5min_*
Return Surplus
AgentType
ExampleExperimentalAgent -768427.709677 -6.968747e+08
The return is the difference between the initial and final cash positions of the agent. The surplus is the difference of the agent's initial cash position from its final holdings (cash + shares marked to the final market price). So it appears that both of these strategies are unprofitable! However, config I does better for surplus and config II does better for return (cash).
This experiment code sits in the script scripts/experimental_agent_demo.sh
for inconvenience.
For a thorough introduction to the ABIDES platform please see the paper ABIDES: Towards High-Fidelity Multi-Agent Market Simulation, for details on the individual agent behaviours see the paper Explaining Agent-Based Financial Market Simulation.
If you'd like more details on the RMSC03 configuration in which we tested this configuration, please see this tutorial. Thanks for following along and happy simulating!