Перейти к основному содержимому

Лоты, база затрат и методы списания в Beancount

Как Beancount учитывает лоты при продаже акций или валют: база затрат, спецификации лотов, методы списания STRICT, FIFO, LIFO и HIFO, цена против стоимости, переопределения для отдельных счетов.

Система запасов Beancount — это мощная функция для отслеживания активов, которые покупаются и продаются с течением времени, таких как акции, паевые фонды или иностранные валюты. Она позволяет точно отслеживать базу затрат, что необходимо для расчёта прироста капитала и понимания эффективности портфеля. Это руководство охватывает основные механики управления запасами в вашем журнале.

Основные понятия​

В своей основе управление запасами вращается вокруг отслеживания позиций. «Позиция» — это просто количество товара (commodity), удерживаемое на счёте. Beancount различает два фундаментальных типа позиций.

Типы позиций​

  1. Простая позиция (без затрат): Это стандартная проводка остатка. Она представляет количество товара без связанных затрат на приобретение. Подходит для денежных средств или простых проверок остатка.

    Assets:Bank:Checking      100.00 USD
  2. Позиция с базой затрат: Этот тип позиции включает не только количество единиц и товар, но и затраты, по которым он был приобретён. Это основа отслеживания запасов. Затраты указываются в фигурных скобках {}.

    Assets:Invest:VTSAX      10 VTSAX {100.00 USD, "lot-1"}

    В этом примере мы удерживаем 10 единиц VTSAX. Каждая единица была приобретена по цене $100.00 USD. Этот конкретный пакет акций идентифицируется как «лот».

Операции с запасами​

Существует две основные операции, которые вы можете выполнять с запасом:

  1. Приращения (добавление в запас): Когда вы покупаете товар, вы увеличиваете свой запас. Вы создаёте новый лот с определённым количеством единиц и базой затрат.

    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.

  2. Уменьшения (удаление из запаса): Когда вы продаёте товар, вы уменьшаете свой запас. Вы должны указать, из какого лота вы продаёте. Это делается путём предоставления соответствующих сведений в фигурных скобках.

    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:Gains

Beancount сообщает 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:FifoFIFOлот A, 2024-01-10$100.00$500.0010 @ $120.00, 10 @ $90.00
Assets:Broker:LifoLIFOлот C, 2024-03-10$90.00$600.0010 @ $100.00, 10 @ $120.00
Assets:Broker:HifoHIFOлот B, 2024-02-10$120.00$300.0010 @ $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: Аннотация, которая фиксирует рыночную цену на момент транзакции. Она используется для конвертации валют или для отметки рыночной стоимости на определённую дату.

Вот три сценария:

  1. Аннотация цены (конвертация): Используйте @ для конвертации из одной валюты в другую.

    Assets:Forex     1000 USD @ 0.85 EUR
  2. База затрат (приобретение): Используйте {} при покупке актива, чтобы установить его затраты.

    Assets:Invest    10 STOCK {100.00 USD}
  3. Оба (продажа с записью цены): При продаже актива используйте {} для идентификации продаваемого лота и @ для записи цены продажи. Это позволяет автоматически рассчитывать прирост капитала.

    Assets:Invest    -10 STOCK {100.00 USD} @ 105.00 USD

    Эта запись продаёт 10 STOCK из лота, который стоил $100.00 за единицу, по цене продажи $105.00 за единицу.

Отдельная директива price предоставляет справочные данные для рыночной оценки. Живые цены могут поддерживать эти директивы для поддерживаемых активов в размещённых журналах. Обновление оставляет ваши лоты, метод бронирования, затраты на приобретение и зафиксированные поступления от продажи без изменений.

Правила использования цены​

  1. Аннотации цен (@) не влияют на то, какой лот бронируется. Сопоставление лотов обрабатывается исключительно базой затрат ({}) и методом бронирования счёта.
  2. Символ @ используется только для:
  • Конвертации валют.
  • Записи рыночной стоимости актива на момент транзакции.
  • Предоставления цены продажи для расчёта прироста капитала.

Конфигурация​

Вы можете настроить методы бронирования глобально или для отдельного счёта.

Глобальный метод учета​

Вы можете установить метод бронирования по умолчанию для всего вашего файла 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"

Рекомендации​

  1. Организация запасов: Чтобы поддерживать ваш журнал в чистоте и простоте, настоятельно рекомендуется использовать отдельные счета для каждого уникального товара, который вы держите, и ограничивать каждый из них этим товаром в его директиве 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 отклонить постороннюю проводку вместо того, чтобы молча смешать два запаса.

  2. Управление лотами:

  • Используйте осмысленные метки для лотов, особенно для конкретных транзакций, таких как налоговая оптимизация убытков или предоставление акций сотрудникам.

    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%
  1. Отладка: Если вы столкнётесь с ошибками или неожиданным поведением, Beancount предоставляет инструменты для проверки состояния вашего запаса.
  • Проверка состояния запаса: Используйте bea doctor context main.beancount 42, чтобы проверить транзакцию в строке 42, включая её проводки и затронутые остатки счетов. Замените имя файла и номер строки на транзакцию, которую хотите проверить.

    Замените <LINENO> на номер строки сразу после транзакции, чтобы увидеть её эффект.

  • Проверка сопоставления лотов: Инструмент bea check проверяет весь ваш файл. Он поймает любые ошибки бронирования, такие как неоднозначные совпадения лотов в режиме STRICT.

Источник: https://beancount.io/ru/docs/Basics/inventories