본문으로 건너뛰기

Beancount 랏(lots), 취득원가 및 부킹 방식

주식이나 통화를 매도할 때 Beancount가 랏을 어떻게 부킹하는지 설명합니다: 취득원가, 랏 스펙, STRICT, FIFO, LIFO, HIFO 부킹, 가격 대비 원가, 계정별 재정의.

Beancount의 재고 시스템은 주식, 뮤추얼 펀드, 외화 등 시간이 지남에 따라 매수하고 매도하는 자산을 추적하기 위한 강력한 기능입니다. 이를 통해 자본 이득 계산과 포트폴리오 성과 파악에 필수적인 취득원가를 정확하게 추적할 수 있습니다. 이 튜토리얼에서는 원장에서 재고를 관리하는 핵심 메커니즘을 다룹니다.

핵심 개념​

재고 관리의 핵심은 포지션 추적에 있습니다. "포지션"이란 단순히 계정에 보유된 특정 상품의 수량입니다. Beancount는 두 가지 기본 포지션 유형을 구분합니다.

포지션 유형​

  1. 단순 포지션 (원가 없음): 이는 표준 잔액 기입입니다. 취득원가가 연결되지 않은 특정 상품의 수량을 나타냅니다. 현금이나 단순한 잔액 검증에 적합합니다.

    Assets:Bank:Checking      100.00 USD
  2. 취득원가가 있는 포지션: 이 유형의 포지션은 단위 수량과 상품뿐만 아니라 취득 당시의 원가도 포함합니다. 이것이 재고 추적의 기초입니다. 원가는 중괄호 {} 안에 명시됩니다.

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

    이 예시에서는 VTSAX 10단위를 보유하고 있습니다. 각 단위는 $100.00 USD의 원가로 취득되었습니다. 이 특정 주식 묶음을 "로트(lot)"라고 합니다.

재고 작업​

재고에 수행할 수 있는 두 가지 주요 연산이 있습니다:

  1. 증가 (재고에 추가): 상품을 매수하면 재고가 증가합니다. 특정 단위 수량과 취득원가를 가진 새로운 로트를 생성합니다.

    2024-01-15 * "주식 매수"
      Assets:Invest:STOCK     50 STOCK {25.00 USD, "lot-1"}
      Assets:Bank:Checking   -1250.00 USD

    여기서 STOCK 50단위를 단위당 $25.00 USD의 원가로 매수합니다. 이렇게 하면 Assets:Invest:STOCK 계정에 로트가 생성됩니다.

  2. 감소 (재고에서 제거): 상품을 매도하면 재고가 감소합니다. 어느 로트에서 매도하는지 명시해야 합니다. 이는 중괄호 안에 일치하는 정보를 제공하여 수행됩니다.

    2024-01-20 * "주식 매도"
      Assets:Invest:STOCK    -25 STOCK {25.00 USD}
      Assets:Bank:Checking    625.00 USD

    이 거래에서는 단위당 $25.00 USD로 매수한 로트에서 STOCK 25단위를 매도합니다.

회계 처리 방식​

재고를 감소시킬 때, 여러 로트가 감소 사항과 일치하는 경우 Beancount는 어느 특정 로트에서 가져올지 결정하는 규칙이 필요합니다. 이 규칙을 "부킹 방법(booking method)"이라고 합니다. option으로 전체 파일에 대한 기본값을 설정하거나, 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 * "첫 번째 로트 매수"
  Assets:Broker:Strict    10 STK {100.00 USD}
  Assets:Broker:Cash   -1000.00 USD
 
2024-02-10 * "두 번째 로트 매수"
  Assets:Broker:Strict    10 STK {120.00 USD}
  Assets:Broker:Cash   -1200.00 USD
 
; 원가가 정확히 하나의 로트를 식별하므로 STRICT를 충족합니다.
2024-06-01 * "$120.00 로트 매도"
  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}를 계정에 남깁니다.

마지막 기입을 빈 지정자로 교체하면 동일한 파일이 실패합니다:

; STRICT에서 거부됨: "-10 STK {}"가 두 로트 모두와 일치합니다.
2024-06-01 * "10주 매도"
  Assets:Broker:Strict  -10 STK {} @ 150.00 USD
  Assets:Broker:Cash   1500.00 USD
  Income:Gains

Beancount는 Ambiguous matches for "-10 STK {}"를 보고하고 후보들을 나열합니다. 그러나 전체 포지션을 매도하는 것은 괜찮습니다. 선택할 것이 남아 있지 않기 때문입니다:

; STRICT에서 허용됨: -20 STK는 전체 보유량이므로 빈
; 지정자가 두 로트에 분할됩니다.
2024-06-01 * "포지션 청산"
  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
 
; 로트 A - 가장 오래됨, 주당 $100.00
2024-01-10 * "로트 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
 
; 로트 B - 가장 비쌈, 주당 $120.00
2024-02-10 * "로트 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
 
; 로트 C - 가장 최근, 주당 $90.00
2024-03-10 * "로트 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
 
; 각 계정에서 10주를 $150.00에 매도하고, 각 계정의
; 부킹 방법이 어느 로트가 나갈지 선택하도록 합니다.
2024-06-01 * "각 계정에서 10주 매도"
  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 * "10주 매수"
  Assets:Broker:Sized    10 STK {100.00 USD}
  Assets:Broker:Cash  -1000.00 USD
 
2024-02-10 * "7주 매수"
  Assets:Broker:Sized     7 STK {120.00 USD}
  Assets:Broker:Cash   -840.00 USD
 
; 정확히 7단위를 보유한 로트가 하나뿐이므로 빈 지정자가 해결됩니다.
2024-06-01 * "7주 매도"
  Assets:Broker:Sized    -7 STK {} @ 150.00 USD
  Assets:Broker:Cash   1050.00 USD
  Income:Gains

이는 $120.00 로트에 대해 $210.00의 이득을 부킹합니다. open 줄에 "STRICT"가 있는 동일한 파일은 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 * "$10.00에 10주 매수"
  Assets:Broker:Avg      10 STK {10.00 USD}
  Assets:Broker:Cash  -100.00 USD
 
2024-02-10 * "$8.00에 10주 추가 매수"
  Assets:Broker:Avg      10 STK {8.00 USD}
  Assets:Broker:Cash   -80.00 USD
 
; 평균원가 엔진이라면これを 주당 $9.00로 부킹할 것입니다. 이것은 거부합니다.
2024-06-01 * "5주 매도"
  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 CLI 도구가 상품을 처리하는 방식과 유사합니다.

로트 지정​

"로트"는 특정 시점과 가격에 취득한 상품의 특정 묶음입니다. 포지션을 생성하거나 감소시킬 때 로트 속성을 자세히 지정할 수 있습니다.

전체 명세​

재고를 증가시킬 때(매수), 하나의 중괄호 쌍 안에 쉼표로 구분하여 최대 세 가지 속성을 지정할 수 있습니다:

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

    이 기입은 각각 $100.00의 원가인 로트에서 STOCK 10주를 각각 $105.00의 매도 가격으로 매도합니다.

독립형 price 지시문은 시장 평가를 위한 참조 데이터를 제공합니다. 실시간 가격은 호스팅 원장에서 지원되는 자산에 대해 이러한 지시문을 유지 관리할 수 있습니다. 새로 고침은 로트, 부킹 방법, 취득원가, 기록된 매도 대금을 변경하지 않습니다.

가격 사용 규칙​

  1. 가격 주석(@)은 어느 로트가 부킹되는지에 영향을 주지 않습니다. 로트 일치는 취득원가({})와 계정의 부킹 방법에 의해서만 처리됩니다.
  2. @ 기호는 다음에만 사용됩니다:
  • 통화 변환.
  • 거래 시점의 자산 시장 가치 기록.
  • 자본 이득 계산을 위한 매도 가격 제공.

구성​

부킹 방법은 전역적으로 또는 계정별로 구성할 수 있습니다.

전역 예약 방식​

option 지시문을 사용하여 전체 Beancount 파일에 대한 기본 부킹 방법을 설정할 수 있습니다.

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 지시문에서 각 계정을 해당 상품으로 제한하는 것이 적극 권장됩니다.

    ; 좋음: 상품별로 별도 계정, 각각 하나로 제한됨
    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 ; 이득: 10%
  1. 디버깅: 오류나 예상치 못한 동작이 발생하면 Beancount는 재고 상태를 검사할 수 있는 도구를 제공합니다.
  • 재고 상태 검사: bea doctor context main.beancount 42를 사용하여 42번 줄의 거래를 검사합니다, 기입과 영향을 받은 계정 잔액을 포함하여. 파일명과 줄 번호를 검사하려는 거래로 교체하세요.

    거래 효과를 확인하려면 <LINENO>를 거래 직후의 줄 번호로 교체하세요.

  • 로트 일치 검증: bea check 도구는 전체 파일을 검증합니다. STRICT 모드의 모호한 로트 일치와 같은 모든 부킹 오류를 잡아냅니다.

출처: https://beancount.io/ko/docs/Basics/inventories