Система запасов Beancount — это мощная функция для отслеживания активов, которые покупаются и продаются с течением времени, таких как акции, паевые фонды или иностранные валюты. Она позволяет точно отслеживать базу затрат, что необходимо для расчёта прироста капитала и понимания эффективности портфеля. Это руководство охватывает основные механики управления запасами в вашем журнале.
Основные понятия
В своей основе управление запасами вращается вокруг отслеживания позиций. «Позиция» — это просто количество товара (commodity), удерживаемое на счёте. Beancount различает два фундаментальных типа позиций.
Типы позиций
-
Простая позиция (без затрат): Это стандартная проводка остатка. Она представляет количество товара без связанных затрат на приобретение. Подходит для денежных средств или простых проверок остатка.
Assets:Bank:Checking 100.00 USD -
Позиция с базой затрат: Этот тип позиции включает не только количество единиц и товар, но и затраты, по которым он был приобретён. Это основа отслеживания запасов. Затраты указываются в фигурных скобках
{}.Assets:Invest:VTSAX 10 VTSAX {100.00 USD, "lot-1"}В этом примере мы удерживаем 10 единиц
VTSAX. Каждая единица была приобретена по цене $100.00 USD. Этот конкретный пакет акций идентифицируется как «лот».
Операции с запасами
Существует две основные операции, которые вы можете выполнять с запасом:
-
Приращения (добавление в запас): Когда вы покупаете товар, вы увеличиваете свой запас. Вы создаёте новый лот с определённым количеством единиц и базой затрат.
2024-01-15 * "Buy shares" Assets:Invest:STOCK 50 STOCK {25.00 USD, "lot-1"} Assets:Bank:Checking -1250.00 USDЗдесь мы покупаем 50 единиц
STOCKпо цене $25.00 USD за единицу. Это создаёт лот на счётеAssets:Invest:STOCK. -
Уменьшения (удаление из запаса): Когда вы продаёте товар, вы уменьшаете свой запас. Вы должны указать, из какого лота вы продаёте. Это делается путём предоставления соответствующих сведений в фигурных скобках.
2024-01-20 * "Sell shares" Assets:Invest:STOCK -25 STOCK {25.00 USD} Assets:Bank:Checking 625.00 USDВ этой транзакции мы продаём 25 единиц
STOCKиз лота, который был приобретён по $25.00 USD за единицу.
Методы учета
Когда вы уменьшаете запас, Beancount нуждается в правиле, чтобы решить, из какого конкретного лота извлекать, если уменьшению соответствует несколько лотов. Это правило называется «методом бронирования». Вы можете установить значение по умолчанию для всего файла с помощью опции или задать собственный метод для отдельного счёта в директиве open.
Beancount 3.2.3 принимает семь названий методов: STRICT (по умолчанию), STRICT_WITH_SIZE, NONE, FIFO, LIFO, HIFO и AVERAGE. Шесть из них реализованы; AVERAGE разбирается, но выдаёт ошибку в тот момент, когда ему нужно забронировать уменьшение, как показывает раздел AVERAGE ниже.
1. STRICT (по умолчанию)
Метод STRICT — это метод по умолчанию и самый безопасный метод бронирования. Он требует явного и однозначного сопоставления.
2024-01-01 open Assets:Invest:STOCK "STRICT"- Требует точного совпадения лота: Спецификатор затрат проводки уменьшения (
{...}) должен идентифицировать единственный лот — по затратам, по дате приобретения, по метке или по любой их комбинации. - Ошибки при неоднозначных совпадениях: Если спецификатор соответствует более чем одному лоту, Beancount вызывает
AmbiguousMatchErrorвместо угадывания. - Исключение: Если уменьшение удаляет ровно общее количество единиц, которому соответствует спецификатор, допускается пустой спецификатор (
{}), и уменьшение распределяется по этим лотам.
Этот журнал содержит два лота и продаёт один из них, указывая его затраты, что однозначно:
1970-01-01 commodity STK
1970-01-01 open Assets:Broker:Strict STK "STRICT"
1970-01-01 open Assets:Broker:Cash USD
1970-01-01 open Income:Gains USD
2024-01-10 * "Buy the first lot"
Assets:Broker:Strict 10 STK {100.00 USD}
Assets:Broker:Cash -1000.00 USD
2024-02-10 * "Buy the second lot"
Assets:Broker:Strict 10 STK {120.00 USD}
Assets:Broker:Cash -1200.00 USD
; The cost identifies exactly one lot, so STRICT is satisfied.
2024-06-01 * "Sell the $120.00 lot"
Assets:Broker:Strict -10 STK {120.00 USD} @ 150.00 USD
Assets:Broker:Cash 1500.00 USD
Income:GainsОн загружается без ошибок, бронирует $300.00 прибыли на Income:Gains и оставляет 10 STK {100.00 USD} на счёте.
Замените эту последнюю проводку на пустой спецификатор, и тот же файл не пройдёт:
; Rejected under STRICT: "-10 STK {}" matches both lots.
2024-06-01 * "Sell 10 shares"
Assets:Broker:Strict -10 STK {} @ 150.00 USD
Assets:Broker:Cash 1500.00 USD
Income:GainsBeancount сообщает Ambiguous matches for "-10 STK {}" и перечисляет кандидатов. Продажа всей позиции, однако, допустима, потому что не остаётся ничего, между чем выбирать:
; Allowed under STRICT: -20 STK is the entire holding, so the empty
; specifier is split across both lots.
2024-06-01 * "Close the position"
Assets:Broker:Strict -20 STK {} @ 150.00 USD
Assets:Broker:Cash 3000.00 USD
Income:GainsЭто бронирует $800.00 прибыли — $3,000.00 поступлений против $1,000.00 + $1,200.00 базы — и оставляет счёт пустым. Это свойство самого STRICT, а не что-то, для чего нужно переключаться на STRICT_WITH_SIZE.
2. FIFO (первым пришел — первым ушел)
Метод FIFO автоматически бронирует уменьшения против самых старых доступных лотов.
2024-01-01 open Assets:Invest:STOCK "FIFO"- Автоматическое разрешение: Он разрешает неоднозначность, выбирая самые старые соответствующие лоты.
- Хронологическое сопоставление: Вы предполагаете, что продаёте активы, которые держали дольше всего. Несколько налоговых органов рассматривают это как метод по умолчанию, когда вы не идентифицировали лот.
3. LIFO (последним пришел — первым ушел)
Метод LIFO противоположен FIFO. Он бронирует уменьшения против самых новых доступных лотов в первую очередь.
2024-01-01 open Assets:Invest:STOCK "LIFO"- Обратный хронологический порядок: Он выбирает самые недавно приобретённые соответствующие лоты.
- Самые новые, а не самые дорогие: LIFO выбирает только по дате приобретения. Он случайно продаёт акции с наибольшими затратами, когда цены росли, но если ваш самый новый лот — ваш самый дешёвый (что и призван показать приведённый ниже пример), LIFO реализует наибольшую прибыль, а не наименьшую. Метод, который всегда продаёт самые дорогие акции, — это
HIFO, описанный далее.
4. HIFO (самый дорогой пришел — первым ушел)
Метод HIFO бронирует уменьшения против самых дорогих доступных лотов в первую очередь, независимо от их даты.
2024-01-01 open Assets:Invest:STOCK "HIFO"- Сопоставление по рангу затрат: Он выбирает соответствующие лоты с наибольшей базой затрат.
- Наименьшая реализованная прибыль: При заданной цене продажи продажа акций с наибольшими затратами реализует наименьшую прибыль (или наибольший убыток). Можно ли его использовать — вопрос юрисдикции — например, в Соединённых Штатах выбор лота вообще требует специфической идентификации в момент продажи, — поэтому рассматривайте метод как механизм бухгалтерского учёта и подтверждайте налоговый выбор отдельно.
5. Сравнение FIFO, LIFO и HIFO на одних и тех же лотах
Эти три метода различаются только тогда, когда самый старый, самый новый и самый дорогой лот — это три разных лота. Этот журнал устроен именно так — лот A самый старый, лот C самый новый, а средний лот B самый дорогой — и затем продаёт 10 акций из трёх счетов, которые отличаются только своим методом бронирования:
1970-01-01 commodity STK
1970-01-01 open Assets:Broker:Fifo STK "FIFO"
1970-01-01 open Assets:Broker:Lifo STK "LIFO"
1970-01-01 open Assets:Broker:Hifo STK "HIFO"
1970-01-01 open Assets:Broker:Cash USD
1970-01-01 open Income:Gains USD
; Lot A - the oldest, at $100.00 per share
2024-01-10 * "Buy lot A"
Assets:Broker:Fifo 10 STK {100.00 USD}
Assets:Broker:Lifo 10 STK {100.00 USD}
Assets:Broker:Hifo 10 STK {100.00 USD}
Assets:Broker:Cash -3000.00 USD
; Lot B - the most expensive, at $120.00 per share
2024-02-10 * "Buy lot B"
Assets:Broker:Fifo 10 STK {120.00 USD}
Assets:Broker:Lifo 10 STK {120.00 USD}
Assets:Broker:Hifo 10 STK {120.00 USD}
Assets:Broker:Cash -3600.00 USD
; Lot C - the newest, at $90.00 per share
2024-03-10 * "Buy lot C"
Assets:Broker:Fifo 10 STK {90.00 USD}
Assets:Broker:Lifo 10 STK {90.00 USD}
Assets:Broker:Hifo 10 STK {90.00 USD}
Assets:Broker:Cash -2700.00 USD
; Sell 10 shares out of each account at $150.00 and let each
; account's booking method choose which lot leaves.
2024-06-01 * "Sell 10 shares from each account"
Assets:Broker:Fifo -10 STK {} @ 150.00 USD
Assets:Broker:Lifo -10 STK {} @ 150.00 USD
Assets:Broker:Hifo -10 STK {} @ 150.00 USD
Assets:Broker:Cash 4500.00 USD
Income:GainsОн загружается без ошибок и бронирует $1,400.00 прибыли в общей сложности, распределённой так:
| Счёт | Метод | Забронированный лот | База затрат | Реализованная прибыль | Оставшиеся лоты |
|---|---|---|---|---|---|
Assets:Broker:Fifo | FIFO | лот A, 2024-01-10 | $100.00 | $500.00 | 10 @ $120.00, 10 @ $90.00 |
Assets:Broker:Lifo | LIFO | лот C, 2024-03-10 | $90.00 | $600.00 | 10 @ $100.00, 10 @ $120.00 |
Assets:Broker:Hifo | HIFO | лот B, 2024-02-10 | $120.00 | $300.00 | 10 @ $100.00, 10 @ $90.00 |
На строку LIFO стоит посмотреть внимательно: она реализовала наибольшую прибыль из трёх, потому что самый новый лот был также самым дешёвым.
6. STRICT_WITH_SIZE
STRICT_WITH_SIZE — это STRICT плюс один дополнительный разрешающий фактор: когда несколько лотов совпадают, но ровно один из них содержит точно то количество единиц, которое вы удаляете, выбирается этот лот.
1970-01-01 commodity STK
1970-01-01 open Assets:Broker:Sized STK "STRICT_WITH_SIZE"
1970-01-01 open Assets:Broker:Cash USD
1970-01-01 open Income:Gains USD
2024-01-10 * "Buy 10 shares"
Assets:Broker:Sized 10 STK {100.00 USD}
Assets:Broker:Cash -1000.00 USD
2024-02-10 * "Buy 7 shares"
Assets:Broker:Sized 7 STK {120.00 USD}
Assets:Broker:Cash -840.00 USD
; Only one lot holds exactly 7 units, so the empty specifier resolves.
2024-06-01 * "Sell 7 shares"
Assets:Broker:Sized -7 STK {} @ 150.00 USD
Assets:Broker:Cash 1050.00 USD
Income:GainsЭто бронирует $210.00 прибыли против лота в $120.00. Идентичный файл с "STRICT" в строке open не пройдёт с Ambiguous matches for "-7 STK {}".
7. AVERAGE (принимается, но не реализован)
AVERAGE — допустимое название — option "booking_method" "AVERAGE" и open … "AVERAGE" оба разбираются — но за ним в Beancount 3.2.3 нет реализации. Всё здесь загружается вплоть до продажи:
1970-01-01 commodity STK
1970-01-01 open Assets:Broker:Avg STK "AVERAGE"
1970-01-01 open Assets:Broker:Cash USD
1970-01-01 open Income:Gains USD
2024-01-10 * "Buy 10 shares at $10.00"
Assets:Broker:Avg 10 STK {10.00 USD}
Assets:Broker:Cash -100.00 USD
2024-02-10 * "Buy 10 more at $8.00"
Assets:Broker:Avg 10 STK {8.00 USD}
Assets:Broker:Cash -80.00 USD
; An average-cost engine would book this at $9.00 per share. This one refuses.
2024-06-01 * "Sell 5 shares"
Assets:Broker:Avg -5 STK {}
Assets:Broker:Cash 45.00 USD
Income:GainsВ тот момент, когда это уменьшение должно быть забронировано, загрузчик останавливается с:
AVERAGE method is not supportedНе планируйте журнал вокруг него. Если вы хотите поведение средней стоимости сегодня, держите позицию на счёте NONE и вычисляйте среднее самостоятельно, или отслеживайте каждый лот и принимайте прибыль на уровне лота.
8. NONE
Метод NONE полностью отключает сопоставление лотов.
2024-01-01 open Assets:Invest:STOCK "NONE"- Без сопоставления лотов: Beancount не пытается сопоставлять уменьшения с приращениями.
- Допускает смешанные знаки: Это позволяет счёту одновременно содержать как положительные, так и отрицательные остатки одного и того же товара. Это поведение похоже на то, как инструмент командной строки Ledger обрабатывает товары.
Спецификация лота
«Лот» — это конкретный блок товара, приобретённый в определённое время и по определённой цене. Когда вы создаёте или уменьшаете позицию, вы можете подробно указать её атрибуты лота.
Полная спецификация
При увеличении запаса (покупке) вы можете указать до трёх атрибутов для лота, разделённых запятыми внутри одной пары фигурных скобок:
Assets:Invest:STOCK 10 STOCK {100.00 USD, 2024-01-15, "lot-identifier"}100.00 USD— база затрат, выраженная за единицу.2024-01-15— дата приобретения. Beancount подставляет её из даты транзакции, когда вы её опускаете, поэтому приведённые выше сообщения об ошибках показывают дату для каждого лота."lot-identifier"— необязательная строковая метка.
Хотя все три необязательны, указание как минимум базы затрат является стандартной практикой. Скобки должны оставаться на одной строке, а комментарии внутри журнала начинаются с ;, никогда с #.
Методы сопоставления
При уменьшении запаса (продаже) вы используете тот же синтаксис, чтобы указать, из какого лота (лотов) продавать.
-
Сопоставление по затратам: Это наиболее распространённый метод.
Assets:Invest:STOCK -5 STOCK {100.00 USD} -
Сопоставление по дате: Если затраты идентичны, вы можете устранить неоднозначность, используя дату приобретения.
Assets:Invest:STOCK -5 STOCK {2024-01-15} -
Сопоставление по метке: Метки обеспечивают безотказный способ идентификации лота.
Assets:Invest:STOCK -5 STOCK {"lot-identifier"} -
Оставить выбор лота методу бронирования: Пустой набор фигурных скобок
{}не называет никакого лота, поэтому выбирает метод бронирования счёта. ПриFIFO,LIFOилиHIFOэто самый старый, самый новый или самый дорогой соответствующий лот; при значении по умолчаниюSTRICTэтоAmbiguousMatchError, если только уменьшение не исчерпывает соответствующие лоты ровно.Assets:Invest:STOCK -5 STOCK {}
Обработка цены
Крайне важно понимать разницу между базой затрат ({}) и ценой (@). Они служат разным целям и не являются взаимозаменяемыми.
Цена против себестоимости
{cost}: Определяет затраты на приобретение актива. Это часть самого лота запаса, и он используется для бронирования уменьшений и расчёта прироста капитала.@ price: Аннотация, которая фиксирует рыночную цену на момент транзакции. Она используется для конвертации валют или для отметки рыночной стоимости на определённую дату.
Вот три сценария:
-
Аннотация цены (конвертация): Используйте
@для конвертации из одной валюты в другую.Assets:Forex 1000 USD @ 0.85 EUR -
База затрат (приобретение): Используйте
{}при покупке актива, чтобы установить его затраты.Assets:Invest 10 STOCK {100.00 USD} -
Оба (продажа с записью цены): При продаже актива используйте
{}для идентификации продаваемого лота и@для записи цены продажи. Это позволяет автоматически рассчитывать прирост капитала.Assets:Invest -10 STOCK {100.00 USD} @ 105.00 USDЭта запись продаёт 10
STOCKиз лота, который стоил $100.00 за единицу, по цене продажи $105.00 за единицу.
Отдельная директива price предоставляет справочные данные для рыночной оценки. Живые цены могут поддерживать эти директивы для поддерживаемых активов в размещённых журналах. Обновление оставляет ваши лоты, метод бронирования, затраты на приобретение и зафиксированные поступления от продажи без изменений.
Правила использования цены
- Аннотации цен (
@) не влияют на то, какой лот бронируется. Сопоставление лотов обрабатывается исключительно базой затрат ({}) и методом бронирования счёта. - Символ
@используется только для:
- Конвертации валют.
- Записи рыночной стоимости актива на момент транзакции.
- Предоставления цены продажи для расчёта прироста капитала.
Конфигурация
Вы можете настроить методы бронирования глобально или для отдельного счёта.
Глобальный метод учета
Вы можете установить метод бронирования по умолчанию для всего вашего файла Beancount с помощью директивы option.
option "booking_method" "STRICT"Принимаемые значения: "STRICT" (по умолчанию, когда вы ничего не задаёте), "STRICT_WITH_SIZE", "NONE", "FIFO", "LIFO", "HIFO" и "AVERAGE". Любая другая строка отклоняется во время загрузки с Error for option 'booking_method'. "AVERAGE" принимается здесь и в open, но бронирование уменьшения при нём не проходит, как показывает раздел AVERAGE выше.
Переопределение для конкретного счета
Часто полезно иметь разные методы для разных счетов. Например, вы можете захотеть FIFO для пенсионного счёта, но STRICT для налогооблагаемого брокерского счёта, чтобы гарантировать продажу конкретных налоговых лотов. Вы можете задать метод бронирования при открытии счёта.
2024-01-01 open Assets:Retirement:401K "FIFO"
2024-01-01 open Assets:Taxable:Stock "STRICT"Рекомендации
-
Организация запасов: Чтобы поддерживать ваш журнал в чистоте и простоте, настоятельно рекомендуется использовать отдельные счета для каждого уникального товара, который вы держите, и ограничивать каждый из них этим товаром в его директиве
open.; GOOD: separate accounts by commodity, each constrained to one 2024-01-01 open Assets:Invest:VTSAX VTSAX 2024-01-01 open Assets:Invest:VFIAX VFIAXИзбегайте смешивания разных акций или фондов на одном счёте, так как это усложняет управление запасами. Список товаров в
openзаставляет Beancount отклонить постороннюю проводку вместо того, чтобы молча смешать два запаса. -
Управление лотами:
-
Используйте осмысленные метки для лотов, особенно для конкретных транзакций, таких как налоговая оптимизация убытков или предоставление акций сотрудникам.
Assets:Invest:STOCK 10 STOCK {100.00 USD, "tax-loss-harvest-2024"} -
Документируйте свои сделки комментариями. Это делает ваш журнал легче для чтения и понимания впоследствии.
Assets:Invest:STOCK -10 STOCK {100.00 USD} @ 110.00 USD ; Gain: 10%
- Отладка: Если вы столкнётесь с ошибками или неожиданным поведением, Beancount предоставляет инструменты для проверки состояния вашего запаса.
-
Проверка состояния запаса: Используйте
bea doctor context main.beancount 42, чтобы проверить транзакцию в строке 42, включая её проводки и затронутые остатки счетов. Замените имя файла и номер строки на транзакцию, которую хотите проверить.Замените
<LINENO>на номер строки сразу после транзакции, чтобы увидеть её эффект. -
Проверка сопоставления лотов: Инструмент
bea checkпроверяет весь ваш файл. Он поймает любые ошибки бронирования, такие как неоднозначные совпадения лотов в режимеSTRICT.