-
Notifications
You must be signed in to change notification settings - Fork 2
Models API Reference
To use oTree Markets, your oTree Models (Subsession
, Group
and Player
) should inherit from the models defined in otree_markets.models
. These models are responsible for communication between the frontend and the exchange. They have several methods and properties that can be overwritten to control the behaviour of your experiment. A complete reference for these models is given below.
asset_names(self)
This method defines the asset names used by your experiment. The default behavior is to trade with only a single asset (named "A"). To enable a multiple-asset trading environment, you should override this method to return a list of strings where each string is the name of an asset being traded. One exchange will be created for each named asset, for each group.
create_exchanges(self)
This method is called when the session is created and is responsible for creating the exchange objects for each named asset. You shouldn't need to overwrite this method, but if you wanted to create extra exchanges for some reason this is where you can do it.
creating_session(self)
This method is built into oTree. It's called when the session is created and is a good place to set up your experiment before it starts. I only include it here to point out that oTree Markets overrides the default oTree method, so if you override creating_session
, you'll have to make sure to call super().creating_session()
exchange_class: otree_markets.exchange.base.BaseExchange
This property refers to the class used to instantiate the exchange objects used for trading. By default, it references otree_markets.exchange.cda_exchange.CDAExchange
. If you want to use a different exchange format, you should subclass otree_markets.exchange.base.BaseExchange
and set exchange_class
to your subclassed exchange.
exchanges: django.db.models.Manager
This property is a Django Manager which references all the exchanges associated with this group. You can use it to query for exchange objects.
For example, to get an iterator of all exchange objects you can use
exchanges = self.exchanges.all()
Or, to get the exchange object for a specific asset you can use
exchange = self.exchanges.get(asset_name='my_asset_name')
If you're only using a single asset, you can get the exchange for that asset by calling get()
with no arguments:
exchange = self.exchanges.get()
Check the Django docs for a more complete description of how database queries work.
period_length(self)
This method sets the length of each trading session. It can be overridden to return a period length in seconds. If not overridden, each period will last indefinitely and will only end when every player leaves the page by clicking the next button or is advanced with the 'Advance slowest user(s)' button on the 'Monitor' tab of the admin page.
Important: Do not use oTree's Page.timeout_seconds
to control the length of trading sessions. Those timeouts start when the player loads the page, not when the trading session actually starts. If you use Page.timeout_seconds
to control the length of your trading sessions, players may be allowed to trade for drastically different amounts of time. Group.period_length
ensures that the timer starts when every player in the group has arrived and is ready to trade.
This method is inherited from otree_redwood.models.Group
. You can read about it here
get_remaining_time(self)
This method returns the total amount of time remaining in the session in seconds.
get_player(self, pcode: str)
This method takes a participant code and returns the Player object for that participant in this group.
This method can be overridden to return None
for certain participant codes. If None is returned, the system will not attempt to access any Player objects associated with that participant code. This can be useful for trading bots which place orders and make trades, but are not associated with an actual player.
_on_enter_event(self, event: otree_redwood.models.Event)
This method is the entry point for enter messages sent by the frontend. Its parameter event
is an oTree redwood Event. The value of this Event is a dict containing the properties of the entered order. You can see the format of this dict in message_types.txt.
An important point is that this method is called before the order is submitted to the exchange, unlike Group.confirm_enter
. By default, this method just calls Player.check_available
to check whether the player has enough cash/asset to submit the order and sends an error if not. If the player can enter the order, it calls exchange.enter_order
on the appropriate exchange object to enter it. You can override this method to add additional conditions to players' ability to enter orders. For example, you could limit traders to only trade on certain assets. In this example, you would check whether event.value['asset_name']
is an allowed asset for that player, and if it isn't then send an error message (with self._send_error
) and return.
_on_cancel_event(self, event: otree_redwood.models.Event)
This method is the entry point for cancel messages sent by the frontend. Its parameter event
is an oTree redwood Event. The value of this Event is a dict containing the properties of the canceled order. You can see the format of this dict in message_types.txt.
This method simply calls the exchange.cancel_order
with the id of the canceled order. Keep in mind that sending a cancel message for an order that isn't yours will raise an exception, as will attempting to cancel an order that isn't currently active.
_on_accept_event(self, event: otree_redwood.models.Event)
This method is the entry point for immediate accept messages sent by the frontend. Its parameter event
is an oTree redwood Event. The value of this Event is a dict containing the properties of the accepted order. You can see the format of this dict in message_types.txt.
An immediate accept is an alternate order type where instead of entering a limit order, a player chooses to directly trade with another order on the exchange. This is similar to a market order, except that the order that is traded with isn't necessarily the best bid/ask. This method just calls Player.check_available
to check whether the player has enough cash/asset to accept the named order and sends an error if not. If the player can afford the order, it calls exchange.accept_immediate
to make the trade.
confirm_enter(self, order: otree_markets.exchange.base.Order)
This method is called by the exchange to confirm that an order was entered. It handles updating players available asset/cash holdings (by calling Player.update_holdings_available
) and sending enter confirmation messages to the frontend. The format of the enter confirmation message sent by this method can be found in message_types.txt.
Keep in mind that this method is only called once an order has been successfully entered into the exchange (unlike Group._on_enter_event
. This method should not be used to prevent players from entering orders, only to react to players successfully entering orders. Also keep in mind that if an order is immediately transacted when it hits the exchange, this method will not be called. This includes immediate accept orders and market orders. When this happens, only Group.confirm_trade
is called. This method specifically reacts to new limit orders being added to the order book.
confirm_trade(self, trade: otree_markets.exchange.base.Trade)
This method is called by the exchange to confirm that a trade occurred. It handles updating players asset/cash holdings (by calling Player.update_holdings_available
and Player.update_holdings_trade
) and sending trade confirmation messages to the frontend. The format of the trade confirmation message sent by this method can be found in message_types.txt.
confirm_cancel(self, order: otree_markets.exchange.base.Order)
This method is called by the exchange to confirm that an order was canceled. It handles updating players available asset/cash holdings (by calling Player.update_holdings_available
) and sending cancel confirmation messages to the frontend. The format of the cancel confirmation message sent by this method can be found in message_types.txt.
Keep in mind that this method is called after an order has been successfully canceled by the exchange (unlike Group._on_cancel_event
). This method should be used to react to orders being successfully canceled. Also, this method is only called when a player actively cancels their order. I.e. it's called when a player sends a cancel message. This method is not called when an order becomes inactive due to being traded.
_send_error(self, pcode: str, message: str)
This method sends an error message to a specific player. The pcode
parameter is the participant code of the player who should receive the error message. The message
parameter is the message that should be delivered.
settled_assets : dict
This property is a dictionary mapping asset names to this player's current settled amount of that asset. To read about the difference between settled and available holdings, view the Terminology page on this wiki. At the end of the round, this property will contain the player's final settled asset holdings.
When playing with a single asset, this property will still be a dictionary, it will just have one entry in it. To get the settled amount of the single asset a player holds, you can use the following snippet:
settled_assets = next(player.settled_assets.values())
For completeness this property is not technically a dict, it's actually a JSONField. But for all intents and purposes, it can be treated as a dict.
available_assets : dict
This property is a dictionary mapping asset names to this player's current available amount of that asset. To read about the difference between settled and available holdings, view the Terminology page on this wiki. At the end of the round, this property will contain the player's final available asset holdings.
When playing with a single asset, this property will still be a dictionary, it will just have one entry in it. To get the available amount of the single asset a player holds, you can use the following snippet:
available_assets = next(player.available_assets.values())
For completeness this property is not technically a dict, it's actually a JSONField. But for all intents and purposes, it can be treated as a dict.
settled_cash: int
This property is an integer representing this player's current settled cash holdings. To read about the difference between settled and available holdings, view the Terminology page on this wiki. At the end of the round, this property will contain the player's final settled cash holdings.
available_cash: int
This property is an integer representing this player's current available cash holdings. To read about the difference between settled and available holdings, view the Terminology page on this wiki. At the end of the round, this property will contain the player's final available cash holdings.
asset_endowment(self)
This method must be overridden. It defines each player's initial endowment of each asset. If your experiment is configured for a single asset (i.e. if Subsession.asset_names
has not been overridden) then this method should return a single int for the player's initial endowment of that asset. If playing with multiple assets, this method should return a dict mapping asset names to the player's initial endowment of that asset.
cash_endowment(self)
This method must be overridden. It defines each player's initial cash endowment. It should return an int representing the player's initial endowment of cash.
set_endowments(self)
This method sets a player's initial cash and asset endowments. It does this by setting self.settled_assets
and self.available_assets
to the result of self.asset_endowment()
, and setting self.settled_cash
and self.available_cash
to the result of self.cash_endowment()
.
You probably won't have any need to override this method.
update_holdings_available(self, order: otree_markets.exchange.base.Order, removed: bool)
This method handles updating players' available holdings when one of their orders is entered or removed from the market.
The order
parameter is the order which triggered an update of the player's available holdings. The removed
parameter is True when order
is leaving the market and False when when order
is entering the market.
update_holdings_trade(self, price: int, volume: int, is_bid: bool, asset_name: str)
This method handles updating players' settled and available holdings when a trade occurs.
price
is the price that the trade occurred at. volume
is the volume of the player's order which was transacted. is_bid
is True if this player's traded order was a bid and false if it's an ask. asset_name
is the name of the asset which was traded.
check_available(self, is_bid: bool, price: int, volume: int, asset_name: str)
This method checks whether a player has enough available holdings to enter an order. It does this by comparing the price/volume of the proposed order to the player's available cash and assets. It returns True if the player has enough available holdings to enter the order and False otherwise.
is_bid
is True if the proposed order is a bid and false if it's an ask. price
is the price of the proposed order. volume
is the volume of the proposed order. asset_name
is the name of the asset that the proposed order is for.
This method may be useful to override for things like allowing short selling. If this method is always made to return True, then players will be allowed to have negative settled and available cash and asset holdings.