Prices¶
This document specifies the price model for plain text accounting systems.
Definition¶
A Price represents the exchange rate between two commodities at a specific point in time.
Price = {
date: Date,
base: Commodity,
quote: Amount
}
Interpretation: On date, 1 unit of base is worth quote.
Terminology¶
| Term | Definition | Example |
|---|---|---|
| Base | The commodity being priced | EUR |
| Quote | The value in another commodity | 1.08 USD |
| Rate | The numeric exchange rate | 1.08 |
| Quote Currency | The commodity of the quote | USD |
Price Directive Syntax¶
Basic Syntax¶
2024-01-15 price EUR 1.08 USD
Meaning: On 2024-01-15, 1 EUR = 1.08 USD
Components¶
<date> price <base> <amount>
date: When this price appliesbase: The commodity being pricedamount: Value per unit (number + quote commodity)
Examples¶
Currency Exchange Rate¶
2024-01-15 price EUR 1.08 USD
2024-01-15 price GBP 1.27 USD
2024-01-15 price JPY 0.0067 USD
Stock Prices¶
2024-01-15 price AAPL 185.92 USD
2024-01-15 price GOOG 140.25 USD
2024-01-15 price MSFT 390.50 USD
Cryptocurrency¶
2024-01-15 price BTC 42500.00 USD
2024-01-15 price ETH 2500.00 USD
Cross Rates¶
2024-01-15 price EUR 0.85 GBP
2024-01-15 price EUR 158.50 JPY
Implicit Prices¶
From Transactions¶
Prices are implicitly recorded from transaction postings:
2024-01-15 * "Currency exchange"
Assets:EUR 100 EUR @ 1.08 USD
Assets:USD
This creates implicit price: 2024-01-15: EUR = 1.08 USD
From Cost Specifications¶
2024-01-15 * "Buy stock"
Assets:Stock 10 AAPL {185.92 USD}
Assets:Cash
This creates implicit price: 2024-01-15: AAPL = 185.92 USD
Price Database¶
Structure¶
The price database stores all known prices:
PriceDatabase = Dict[
(Commodity, Commodity), # (base, quote)
List[(Date, Decimal)] # sorted by date
]
Operations¶
| Operation | Description |
|---|---|
add(date, base, quote, rate) |
Add a price point |
get(date, base, quote) |
Get price on date |
latest(base, quote) |
Get most recent price |
range(start, end, base, quote) |
Get prices in range |
Price Lookup¶
Exact Match¶
Find price on specific date:
price = db.get(date=2024-01-15, base="EUR", quote="USD")
# Returns: 1.08
Fallback to Previous¶
If no price on exact date, use most recent:
# No price on 2024-01-16
# Falls back to 2024-01-15 price
price = db.get(date=2024-01-16, base="EUR", quote="USD")
# Returns: 1.08 (from 2024-01-15)
No Price Available¶
price = db.get(date=2024-01-15, base="XYZ", quote="USD")
# Returns: None (no price history for XYZ)
Inverse Prices¶
Automatic Inversion¶
If only one direction is stored, the inverse can be computed:
Stored: 2024-01-15 price EUR 1.08 USD
Query: USD → EUR
Computed: 1 USD = 1/1.08 EUR ≈ 0.926 EUR
Implementation¶
def get_price(base, quote, date):
# Try direct lookup
if (base, quote) in prices:
return lookup(base, quote, date)
# Try inverse
if (quote, base) in prices:
rate = lookup(quote, base, date)
return 1 / rate
return None
Price Chains¶
Indirect Conversion¶
When no direct price exists, chain through intermediate:
Stored:
2024-01-15 price EUR 1.08 USD
2024-01-15 price GBP 1.27 USD
Query: EUR → GBP
Computed: 1 EUR = 1.08 USD = 1.08/1.27 GBP ≈ 0.850 GBP
Chain Limits¶
Implementations MAY limit chain length:
Max chain length: 2 (base → intermediate → quote)
Market Value Calculation¶
Single Commodity¶
holdings = 100 EUR
price = 1.08 USD/EUR
market_value = 100 × 1.08 = 108 USD
Portfolio Valuation¶
portfolio = {
"USD": 1000,
"EUR": 500,
"AAPL": 10
}
prices = {
"EUR": 1.08 USD,
"AAPL": 185.92 USD
}
market_value = (
1000 + # USD (base currency)
500 × 1.08 + # EUR → USD
10 × 185.92 # AAPL → USD
) = 1000 + 540 + 1859.20 = 3399.20 USD
Price Sources¶
Manual Entry¶
2024-01-15 price AAPL 185.92 USD
Transaction-Derived¶
2024-01-15 * "Trade"
Assets:Stock 10 AAPL @ 185.92 USD
...
External Fetch¶
Tools may fetch prices from external sources:
bean-price ledger.beancount
Price Metadata¶
Source Tracking¶
2024-01-15 price AAPL 185.92 USD
source: "Yahoo Finance"
time: "16:00 EST"
Bid/Ask Spread¶
2024-01-15 price EUR 1.08 USD
bid: 1.0795
ask: 1.0805
Temporal Considerations¶
Point-in-Time¶
Prices are point-in-time snapshots:
2024-01-14 price AAPL 184.00 USD ; Yesterday
2024-01-15 price AAPL 185.92 USD ; Today
2024-01-16 price AAPL 187.50 USD ; Tomorrow
Intraday Prices¶
Some systems support intraday prices:
2024-01-15 price AAPL 185.00 USD
time: "09:30"
2024-01-15 price AAPL 185.92 USD
time: "16:00"
End-of-Day Convention¶
Most PTA systems use end-of-day prices:
; Price applies to entire day
2024-01-15 price AAPL 185.92 USD
Unrealized Gains¶
Calculation¶
Cost basis: 10 AAPL @ 150 USD = 1500 USD
Market value: 10 × 185.92 USD = 1859.20 USD
Unrealized gain: 1859.20 - 1500 = 359.20 USD
Booking Entry¶
2024-01-15 * "Mark to market"
Assets:Stock 359.20 AAPL-GAIN
Income:Unrealized-Gains -359.20 USD
Validation¶
Invalid Price¶
ERROR: Invalid price directive
--> ledger.beancount:42:1
|
42 | 2024-01-15 price AAPL -185 USD
| ^^^^
|
= price cannot be negative
Unknown Commodity¶
WARNING: Unknown commodity in price
--> ledger.beancount:42:15
|
42 | 2024-01-15 price XYZ 100 USD
| ^^^
|
= no other references to XYZ found
Implementation Model¶
@dataclass
class Price:
date: date
base: str
quote: Amount
@property
def rate(self) -> Decimal:
return self.quote.number
@property
def quote_currency(self) -> str:
return self.quote.commodity
class PriceDatabase:
def __init__(self):
self._prices: Dict[Tuple[str, str], List[Tuple[date, Decimal]]] = {}
def add(self, price: Price):
key = (price.base, price.quote_currency)
if key not in self._prices:
self._prices[key] = []
bisect.insort(self._prices[key], (price.date, price.rate))
def get(self, base: str, quote: str, as_of: date) -> Optional[Decimal]:
key = (base, quote)
if key in self._prices:
prices = self._prices[key]
# Find most recent price <= as_of
idx = bisect.bisect_right(prices, (as_of, Decimal('inf'))) - 1
if idx >= 0:
return prices[idx][1]
# Try inverse
inverse_key = (quote, base)
if inverse_key in self._prices:
prices = self._prices[inverse_key]
idx = bisect.bisect_right(prices, (as_of, Decimal('inf'))) - 1
if idx >= 0:
return 1 / prices[idx][1]
return None
Serialization¶
Text Format¶
2024-01-15 price EUR 1.08 USD
JSON Format¶
{
"date": "2024-01-15",
"base": "EUR",
"quote": {
"number": "1.08",
"commodity": "USD"
}
}
Cross-Format Notes¶
| Feature | Beancount | Ledger | hledger |
|---|---|---|---|
| Directive | price |
P |
P |
| Syntax | price BASE AMOUNT |
P DATE BASE RATE QUOTE |
P DATE BASE RATE QUOTE |
| Implicit | From @ prices |
From @ prices |
From @ prices |
| Inversion | Automatic | Manual | Automatic |
| Chains | Limited | No | No |