Skip to content

Models API Reference

Morgan Grant edited this page Nov 12, 2020 · 9 revisions

API Reference for oTree Markets Models

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.

Table of Contents

models.Subsession

Methods

Subsession.asset_names

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.

Subsession.create_exchanges

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.

Subsession.creating_session

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()

models.Group

Properties

Group.exchange_class

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.

Group.exchanges

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.

Methods

Group.period_length

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

Group.get_remaining_time

get_remaining_time(self)

This method returns the total amount of time remaining in the session in seconds.

Group.get_player

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.

Group._on_enter_event

_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.

Group._on_cancel_event

_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.

Group._on_accept_event

_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.

Group.confirm_enter

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.

Group.confirm_trade

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.

Group.confirm_cancel

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.

Group._send_error

_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.

models.Player

Properties

Player.settled_assets

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.

Player.available_assets

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.

Player.settled_cash

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.

Player.available_cash

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.

Methods

Player.asset_endowment

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.

Player.cash_endowment

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.

Player.set_endowments

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.

Player.update_holdings_available

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.

Player.update_holdings_trade

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.

Player.check_available

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.