Currency and Decimal
Currency
In many experiments, participants play for currency: either real money, or points.
Configuring currency
Here is how to configure what currency you use (points, dollars, euros, etc.).
If you are using oTree 6+ it’s recommended to define a DecimalUnit class in units.py,
(see the section on DecimalUnit for more info):
class USD(DecimalUnit):
storage_places = 4
input_places = 0
input_unit_label = '$'
output_min_places = 0
output_max_places = 2
@staticmethod
def output(formatted, raw):
return f"${formatted}"
Then in settings.py put the path to this class:
CURRENCY_UNIT = 'units.USD'
If you are using oTree 5, then you should set the REAL_WORLD_CURRENCY_CODE and USE_POINTS settings.
Using currencies
You can write cu(42) to represent “42 currency units”.
It works just like a number
(e.g. cu(0.1) + cu(0.2) == cu(0.3)).
The advantage is that when it’s displayed to users, it will automatically
be formatted as $0.30 or 0,30 €, etc., depending on your
REAL_WORLD_CURRENCY_CODE and LANGUAGE_CODE settings.
Use CurrencyField to store currencies in the database.
For example:
class Player(BasePlayer):
random_bonus = models.CurrencyField()
To make a list of currency amounts, use currency_range:
currency_range(0, 0.10, 0.02)
# this gives:
# [$0.00, $0.02, $0.04, $0.06, $0.08, $0.10]
In templates, instead of using the cu() function, you should use the
|cu filter.
For example, {{ 20|cu }} displays as 20 points.
payoffs
Each player has a payoff field.
If your player makes money, you should store it in this field.
participant.payoff automatically stores the sum of payoffs
from all subsessions. You can modify participant.payoff directly,
e.g. to round the final payoff to a whole number.
At the end of the experiment, a participant’s
total profit can be accessed by participant.payoff_plus_participation_fee().
DecimalField
Note
To use this, you must install oTree 6.0
DecimalField is based on the Python Decimal datatype,
which can represent base-10 numbers exactly and therefore avoids annoying arithmetic errors that occur with float.
(You can find lots of info online about this subject.)
When defining a DecimalField, you specify unit= to indicate what entity it represents,
e.g.:
xyz = models.DecimalField(unit=units.Celsius)
And create units.py in your project root folder and import that into your app.
units.py should have content like this:
from otree.api import DecimalUnit
class Celsius(DecimalUnit):
storage_places = 4
output_max_places = 2
output_min_places = 0
input_places = 0
input_unit_label = '°C'
This lets you separately configure the precision used for input (participant filling a form), storage (internal calculations and database), and output (displaying in a template).
storage_placesis the number of decimal places used internally (for database storage and calculations). If you setstorage_places=6, then1/3will be stored as0.333333.The
output_properties apply when displaying the content in a template. If you setoutput_max_places=2andoutput_min_places=0, then9.876will display as9.87. but9.000will display as9(remove trailing zeros).The
input_properties are relevant if the field is included in a form. If you setinput_places=0, then the user must input a whole number.input_unit_labelsets the label on the right edge of the number input.
You can also define a function that will generate the display value.
It takes 2 arguments: the formatted value (e.g. "1,234.5") and the raw numeric value:
class Celsius(DecimalUnit):
# ...
@staticmethod
def output(formatted, raw):
if raw < 10:
color = 'blue'
elif raw < 20:
color = 'green'
elif raw < 30:
color = 'yellow'
else:
color = 'red'
return f"<span style='color: {color};'>{formatted}°C</span>"
Decimal datatype
Apart from database fields,
you can define decimal values throughout your code by calling the unit type directly,
e.g. ROOM_TEMP = units.Celsius(25).