Fixed-Point Trading Value Types: Price, Quantity, and Money
Summary
This documentation explains three trading-specific numeric types: Price for market levels, Quantity for non-negative sizes, and Money for signed amounts associated with a currency. The types are immutable and use fixed-point representation to support deterministic arithmetic. Their operators preserve domain types where appropriate, while operations that change units or combine values with ordinary numbers return Decimal or float according to the operand types.
Precision controls formatting and serialization metadata, while numeric equality depends on the represented value. Arithmetic between values of differing precision uses the greater precision. The document also describes constraints: quantities cannot be negative, and monetary addition or subtraction requires matching currencies. It notes that float conversions can lose precision, especially at high precision, and recommends Decimal conversion when exactness matters. These rules help prevent unit and rounding mistakes in trading systems, but the material is API documentation rather than a trading method or empirical analysis; it does not establish that a particular strategy or system will perform better by adopting these types.
Key ideas
- Price, Quantity, and Money are immutable types designed to represent distinct trading quantities.
- Fixed-point storage supports deterministic calculations, while precision metadata controls display and serialization.
- Quantity values cannot be negative, and Money arithmetic requires matching currencies.
- Operators may return Decimal or float when arithmetic changes units or mixes value types with scalars.
- Float conversion may round, so Decimal conversion is preferable when exact values are needed.
Tags
Full text
# Value Types
# Value Types
NautilusTrader provides specialized value types for representing core trading concepts:
`Price`, `Quantity`, and `Money`. These types use fixed-point arithmetic internally
for performant, deterministic calculations across different platforms
and environments.
## Overview
| Type | Purpose | Signed | Currency |
| ---------- | ---------------------------------------- | ------ | -------- |
| `Quantity` | Trade sizes, order amounts, positions. | No | - |
| `Price` | Market prices, quotes, price levels. | Yes | - |
| `Money` | Monetary amounts, P&L, account balances. | Yes | Yes |
## Immutability
In Python, all value types are **immutable**. Once a value is constructed, it cannot be changed.
Operations do not mutate the original object.
```python
from nautilus_trader.model import Quantity
qty1 = Quantity(100, precision=0)
qty2 = Quantity(50, precision=0)
# This creates a NEW Quantity; qty1 and qty2 are unchanged
result = qty1 + qty2
print(qty1) # 100
print(qty2) # 50
print(result) # 150
```
This design provides several benefits:
- **Thread safety**: Immutable values can be safely shared across threads without synchronization.
- **Predictability**: Values never change unexpectedly, making debugging easier.
- **Hashability**: Immutable types can be used as dictionary keys and in sets.
## Arithmetic operations
Value types support standard arithmetic operators (`+`, `-`, `*`, `/`, `%`, `//`)
and unary operators (`-`, `+`, `abs`). The return type depends on the operator
and the operand types.
### Same-type binary operations
Addition and subtraction of the same value type return that type, preserving
domain meaning (a price plus a price is still a price):
| Operation | Result |
| --------------------- | ---------- |
| `Quantity + Quantity` | `Quantity` |
| `Quantity - Quantity` | `Quantity` |
| `Price + Price` | `Price` |
| `Price - Price` | `Price` |
| `Money + Money` | `Money` |
| `Money - Money` | `Money` |
```python
from nautilus_trader.model import Price
price1 = Price(100.50, precision=2)
price2 = Price(0.25, precision=2)
result = price1 + price2 # Returns Price(100.75, precision=2)
print(type(result)) # <class 'nautilus_trader.model.Price'>
```
Multiplication, division, floor division, and modulo between two values of the
same type return `Decimal`:
| Operation | Result |
| ---------------- | --------- |
| `Price * Price` | `Decimal` |
| `Price / Price` | `Decimal` |
| `Price // Price` | `Decimal` |
| `Price % Price` | `Decimal` |
The same pattern applies to `Quantity` and `Money`.
These operations do not return the original type because the result has different
dimensional meaning. Multiplying a price by a price produces "price squared", not
a price. Dividing a quantity by a quantity produces a dimensionless ratio, not a
quantity. Returning `Decimal` makes the unit change explicit and prevents
misinterpretation of the result as a value with the original unit.
### Unary operations
Unary operators preserve the value type where the result is valid for that type:
| Operation | `Price` | `Quantity` | `Money` |
| ---------- | --------- | ---------- | --------- |
| `-x` (neg) | `Price` | `Decimal` | `Money` |
| `+x` (pos) | `Price` | `Quantity` | `Money` |
| `abs(x)` | `Price` | `Quantity` | `Money` |
| `int(x)` | `int` | `int` | `int` |
| `float(x)` | `float` | `float` | `float` |
| `round(x)` | `Decimal` | `Decimal` | `Decimal` |
`Quantity.__neg__` returns `Decimal` rather than `Quantity` because `Quantity` is
unsigned and cannot represent a negative value.
```python
from nautilus_trader.model import Currency, Money, Price, Quantity
USD = Currency.from_str("USD")
price = Price(100.50, precision=2)
print(-price) # -100.50
print(type(-price)) # <class 'nautilus_trader.model.Price'>
money = Money(-50.00, USD)
print(abs(money)) # 50.00 USD
print(type(abs(money))) # <class 'nautilus_trader.model.Money'>
qty = Quantity(10, precision=0)
print(+qty) # 10
print(type(+qty)) # <class 'nautilus_trader.model.Quantity'>
```
### Mixed-type operations
When operating with other numeric types, the result type follows Python's
[numeric tower](https://docs.python.org/3/library/numbers.html) conventions. The general
principle is that operations widen to the more general type: `float` operations return
`float`, while `int` and `Decimal` operations return `Decimal` for precision preservation.
This applies to all six binary operators (`+`, `-`, `*`, `/`, `//`, `%`) and works
in both directions (`value op scalar` and `scalar op value`):
| Left operand | Right operand | Result type |
| ------------ | ------------- | ----------- |
| Value type | `int` | `Decimal` |
| Value type | `float` | `float` |
| Value type | `Decimal` | `Decimal` |
| `int` | Value type | `Decimal` |
| `float` | Value type | `float` |
| `Decimal` | Value type | `Decimal` |
```python
from decimal import Decimal
from nautilus_trader.model import Quantity
qty = Quantity(100, precision=0)
# Quantity + int -> Decimal
result1 = qty + 50
print(type(result1)) # <class 'decimal.Decimal'>
# Quantity + float -> float
result2 = qty + 50.5
print(type(result2)) # <class 'float'>
# Quantity + Decimal -> Decimal
result3 = qty + Decimal("50")
print(type(result3)) # <class 'decimal.Decimal'>
```
## Precision handling
Each value type carries a precision indicating the number of decimal places:
`Price` and `Quantity` store an explicit `precision` field, while `Money` uses its
currency's precision. Precision is set at construction and is immutable. There is
no "unspecified" precision.
### Fixed-point representation
Value types are stored internally as integers scaled to a global fixed precision
(e.g., 10^16 in high-precision mode), not floating-point numbers. The precision
tracks the number of decimal places used at construction, controlling display
formatting and serialization, but the underlying raw value always uses the global scale.
```python
from nautilus_trader.model import Price
p1 = Price(1.23, precision=2) # displays as "1.23"
p2 = Price(1.230, precision=3) # displays as "1.230"
p1 == p2 # True: same underlying value
str(p1) # "1.23"
str(p2) # "1.230"
```
**Precision controls display, not identity.** Two prices with the same decimal value but
different precisions are equal. The `precision` field determines string formatting and
how many decimal places are shown, but equality is based on the underlying numeric value.
**Market data serialization uses precision metadata.** When market data types (quotes,
trades, order book deltas) are written to Parquet or Arrow format, precision is stored in
the file metadata so that values can be correctly decoded. All market data values within
a single file must share the same precision.
:::warning
If a venue changes an instrument's tick size (and thus its precision), data files written
before and after the change will have different precision metadata and should not be
consolidated into a single file.
:::
For how instrument-level precision constrains valid prices and quantities, see the
[Precision](instruments/index.md#precision) section of the Instruments guide.
### Arithmetic precision
When performing arithmetic between values with different precisions, the result
uses the maximum precision of the operands.
```python
from nautilus_trader.model import Price
price1 = Price(100.5, precision=1) # 1 decimal place
price2 = Price(0.125, precision=3) # 3 decimal places
result = price1 + price2
print(result) # 100.625
print(result.precision) # 3 (max of 1 and 3)
```
## Type-specific constraints
### Quantity
`Quantity` represents non-negative amounts. Attempting to create a negative quantity
or subtract a larger quantity from a smaller one raises an error:
```python
from nautilus_trader.model import Quantity
# This raises ValueError: Quantity cannot be negative
qty = Quantity(-100, precision=0)
# This also raises ValueError
qty1 = Quantity(50, precision=0)
qty2 = Quantity(100, precision=0)
result = qty1 - qty2 # Would be -50, which is invalid
```
### Money
`Money` values include a currency. Addition and subtraction between `Money` values
require **matching currencies**:
```python
from nautilus_trader.model import Currency, Money
USD = Currency.from_str("USD")
EUR = Currency.from_str("EUR")
usd_amount = Money(100.00, USD)
eur_amount = Money(50.00, EUR)
# This works - same currency
result = usd_amount + Money(25.00, USD)
# This raises ValueError - currency mismatch
result = usd_amount + eur_amount
```
## Common patterns
### Accumulating values
Since value types are immutable, accumulate by reassigning:
```python
from nautilus_trader.model import Currency, Money
USD = Currency.from_str("USD")
total = Money(0.00, USD)
amounts = [Money(100.00, USD), Money(50.00, USD), Money(25.00, USD)]
for amount in amounts:
total = total + amount # Reassign to new Money instance
print(total) # 175.00 USD
```
### Converting to other types
Value types provide conversion methods:
```python
from nautilus_trader.model import Price
price = Price(123.456, precision=3)
# Convert to Decimal (preserves precision)
decimal_value = price.as_decimal()
# Convert to float
float_value = price.as_double()
# Convert to string
string_value = str(price) # "123.456"
```
Float conversion supports precision up to 16. `float()`, `as_double()`, and arithmetic with a
`float` operand raise `ValueError` for precision 17 or 18 rather than round away the extra digits.
Conversions at precision 16 or below can still round, since a float holds about 16 significant
digits. Use `as_decimal()` for an exact value.
### Creating from strings
Parse value types from string representations:
```python
from nautilus_trader.model import Money, Price, Quantity
qty = Quantity.from_str("100.5")
price = Price.from_str("99.95")
money = Money.from_str("1000.00 USD")
```Shown in full with attribution under the source's licence. Licence: LGPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.