Skip to content

Creating Market Interfaces

Morgan Grant edited this page Nov 13, 2020 · 6 revisions

Creating Market Interfaces

Creating an interface for your market experiment can be a difficult task. These interfaces need to communicate a lot of information, so naturally they end up being relatively complex. oTree Markets comes with several plug-and-play components that greatly simplify this task, though you may still have difficulty getting things set up without at least a moderate amount of experience with HTML/Javascript.

If you don't have any Javascript skills, all is not lost! We have two pre-built market experiments that should at least get you close to what you need. You can see those on the examples page. Even if you do have Javascript chops, starting with one of those experiments and modifying it to suit your needs will probably be easier than starting from scratch.

Frontend Basics

If the word "frontend" is unfamiliar to you, check out the Terminology page.

When you create a fresh oTree experiment, your experiment folder will have the following essential directory structure. This example assumes the newly-created experiment is named "my_experiment".

* my_experiment
  * templates
    * my_experiment
      - MyPage.html
      - Results.html
  - models.py
  - pages.py

The Pages specified in pages.py should have the same name as one of the templates in your templates folder. In a basic oTree experiment, these templates are where you design and write all of your frontend. This works fine for simple experiments, but in a more complex experiment you'll probably want to split your frontend into multiple files. To do this, you'll want to add some static files.

You can think of static files as "addons" to your frontend. They're little bundles of Javascript or HTML you can include to add functionality to your experiment. To add static files to your experiment you'll have to add some folders:

* my_experiment
  * templates
    * my_experiment
      - MyPage.html
      - Results.html
  * static
    * my_experiment
      - my_static_file.js
  - models.py
  - pages.py

You can see that we added a folder called static, and added another folder inside it with the same name as our experiment. In that folder, we can put whatever static files we want to include in our experiment. Here we've added a Javascript file called my_static_file.js.

Now imagine my_static_file.js has the following contents:

// my_static_file.js

function myFunction() {
    console.log('Hello World!');
}

Now we can include this file into our template and call myFunction:

<!-- MyPage.html -->

{% extends "global/Page.html" %}
{% load otree static %}

{% block title %}
    Page title
{% endblock %}

{% block scripts %}
    <script src="/static/my_experiment/my_static_file.js"></script>

    <script>
        myFunction();
    </script>
{% endblock %}

{% block content %}

    {% next_button %}

{% endblock %}

You can see that we added a section in our template for Javascript scripts using {% block scripts %} and {% endblock %}. Inside that section we created two script tags, one with src="/static/my_experiment/my_static_file.js" and another that contains a call to myFunction. If you run this, you should see "Hello World!" appear in the console.

The important thing to note here is that anything we put in our new static/my_experiment folder can be accessed from the path /static/my_experiment/. You can read more about static files in the oTree docs here.

Keep in mind that anything we include from our static files (like my_static_file.js) won't have access to any of the template variables we put into vars_for_template. This means that we can't use the same {{ }} syntax that we use in our template to get data from the backend. If you want to use variables from the backend in this way, you'll have to somehow get them into Javascript through your template.

Polymer.js

Now that we have a grip on using static files to add Javascript to our experiment, we can take a look at the way oTree Markets' frontend elements are constructed.

All of the frontend components for oTree Markets are built using a library called Polymer.js. This library is a framework that allows us to neatly bundle Javascript and HTML into single-purpose components. It isn't required to use Polymer to build your frontend, but some of its features make the process of handling and updating market data much simpler. We won't go into detail about Polymer here, so if you'd like to use it you should take a look at the docs before starting.

Polymer is included with otree-redwood, which is one of oTree Markets' requirements. You can import Polymer into a Javascript file with the following expression:

import { html, PolymerElement } from '/static/otree-redwood/node_modules/@polymer/polymer/polymer-element.js';

An example of a minimal Polymer component built for oTree Markets:

// my_polymer_component.js

import { html, PolymerElement } from '/static/otree-redwood/node_modules/@polymer/polymer/polymer-element.js';

class MyPolymerComponent extends PolymerElement {
    static get template() {
        return html`
            <div>Hello World!</div>
        `;
    }
}

window.customElements.define('my-polymer-component', MyPolymerComponent);

Assuming this file is called my_polymer_component.js and is included in your experiment's static folder at static/my_experiment/my_polymer_component.js, this component can be included in your template in the following way:

<!-- MyPage.html -->

{% extends "global/Page.html" %}
{% load otree static %}

{% block title %}
    Page title
{% endblock %}

{% block scripts %}
    <script type="module" src="/static/my_experiment/my_polymer_component.js"></script>
{% endblock %}

{% block content %}

    <my-polymer-component></my-polymer-component>

    {% next_button %}

{% endblock %}

If you run this, you should see the text "Hello World!" displayed above the next button on your page.

Using oTree Markets Builtin Components

oTree Markets comes with several pre-built Polymer components that you can use to create interfaces, the most important of which is the trader-state component. You can find a reference for all the other frontend components here.

If you're using Polymer to create your frontend, the helper elements should be essentially plug-and-play, though you'll have to do all the layout and CSS yourself. As an example, we'll look at how to use Polymer's data binding facilities to connect the order-list component to trader-state.

// my_polymer_component.js

import { html, PolymerElement } from '/static/otree-redwood/node_modules/@polymer/polymer/polymer-element.js';

import '/static/otree_markets/trader_state.js';
import '/static/otree_markets/order_list.js';

class MyPolymerComponent extends PolymerElement {

    static get properties() {
        return {
            bids: Array,
            asks: Array,
        };
    }

    static get template() {
        return html`
            <trader-state bids="{{ bids }}" asks="{{ asks }}">
            </trader-state>

            <order-list orders="[[ bids ]]"></order-list>
            <order-list orders="[[ asks ]]"></order-list>
        `;
    }
}

window.customElements.define('my-polymer-component', MyPolymerComponent);

This component can be included in your template in the following way. Again assume this component is located in static/my_experiment/my_polymer_component.js.

<!-- MyPage.html -->

{% extends "otree_markets/Page.html" %}
{% load otree static %}

{% block title %}
    Page title
{% endblock %}

{% block scripts %}
    <script type="module" src="/static/my_experiment/my_polymer_component.js"></script>
{% endblock %}

{% block content %}

    <my-polymer-component></my-polymer-component>

{% endblock %}

This is very similar to the previous template, the only differences are that it extends otree_markets/Page.html instead of global/Page.html, and we've removed the next button. Extending otree_markets/Page.html is required for trader-state to work correctly, and the next button is remove because page advancing is automatically handled by oTree Markets (assuming you set Group.period_length

If you actually run this, it probably won't look very good because it's lacking any kind of layout. But hopefully it illustrates the core steps required to use these components.

First we import Polymer, the trader-state and order-list components:

import { html, PolymerElement } from '/static/otree-redwood/node_modules/@polymer/polymer/polymer-element.js';

import '/static/otree_markets/trader_state.js';
import '/static/otree_markets/order_list.js';

Then we create two Array properties on our Polymer component to store the current sets of bids and asks. When we bind these properties to trader-state, they'll automatically be updated whenever a new order is added to the book.

static get properties() {
    return {
        bids: Array,
        asks: Array,
    };
}

In our template, we include trader-state and use two-way bindings to connect trader-state's bid and ask properties to our component's bid and ask properties:

<trader-state bids="{{ bids }}" asks="{{ asks }}">
</trader-state>

Below this, we create two order-list components and use one-way bindings to attach our component's bid and ask properties to them:

<order-list orders="[[ bids ]]"></order-list>
<order-list orders="[[ asks ]]"></order-list>

Further Reading

If you're looking to create your own interface, it'll probably be illuminative to look at how our example experiments are designed. These are complete examples, with many of the components included and actual layouts.

Again, it's likely that the experiment you're building will be somewhat similar to one of the example experiments. It's a good idea to use one of them as a starting point to save yourself some trouble.