Beancount의 재고 시스템은 주식, 뮤추얼 펀드, 외화 등 시간이 지남에 따라 매수하고 매도하는 자산을 추적하기 위한 강력한 기능입니다. 이를 통해 자본 이득 계산과 포트폴리오 성과 파악에 필수적인 취득원가를 정확하게 추적할 수 있습니다. 이 튜토리얼에서는 원장에서 재고를 관리하는 핵심 메커니즘을 다룹니다.
핵심 개념
재고 관리의 핵심은 포지션 추적에 있습니다. "포지션"이란 단순히 계정에 보유된 특정 상품의 수량입니다. Beancount는 두 가지 기본 포지션 유형을 구분합니다.
포지션 유형
-
단순 포지션 (원가 없음): 이는 표준 잔액 기입입니다. 취득원가가 연결되지 않은 특정 상품의 수량을 나타냅니다. 현금이나 단순한 잔액 검증에 적합합니다.
Assets:Bank:Checking 100.00 USD -
취득원가가 있는 포지션: 이 유형의 포지션은 단위 수량과 상품뿐만 아니라 취득 당시의 원가도 포함합니다. 이것이 재고 추적의 기초입니다. 원가는 중괄호
{}안에 명시됩니다.Assets:Invest:VTSAX 10 VTSAX {100.00 USD, "lot-1"}이 예시에서는
VTSAX10단위를 보유하고 있습니다. 각 단위는 $100.00 USD의 원가로 취득되었습니다. 이 특정 주식 묶음을 "로트(lot)"라고 합니다.
재고 작업
재고에 수행할 수 있는 두 가지 주요 연산이 있습니다:
-
증가 (재고에 추가): 상품을 매수하면 재고가 증가합니다. 특정 단위 수량과 취득원가를 가진 새로운 로트를 생성합니다.
2024-01-15 * "주식 매수" Assets:Invest:STOCK 50 STOCK {25.00 USD, "lot-1"} Assets:Bank:Checking -1250.00 USD여기서
STOCK50단위를 단위당 $25.00 USD의 원가로 매수합니다. 이렇게 하면Assets:Invest:STOCK계정에 로트가 생성됩니다. -
감소 (재고에서 제거): 상품을 매도하면 재고가 감소합니다. 어느 로트에서 매도하는지 명시해야 합니다. 이는 중괄호 안에 일치하는 정보를 제공하여 수행됩니다.
2024-01-20 * "주식 매도" Assets:Invest:STOCK -25 STOCK {25.00 USD} Assets:Bank:Checking 625.00 USD이 거래에서는 단위당 $25.00 USD로 매수한 로트에서
STOCK25단위를 매도합니다.
회계 처리 방식
재고를 감소시킬 때, 여러 로트가 감소 사항과 일치하는 경우 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:GainsBeancount는 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: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 * "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: 거래 시점의 시장 가격을 기록하는 주석입니다. 통화 변환이나 특정 날짜의 시장 가치를 기록하는 데 사용됩니다.
세 가지 시나리오가 있습니다:
-
가격 주석 (변환):
@를 사용하여 한 통화에서 다른 통화로 변환합니다.Assets:Forex 1000 USD @ 0.85 EUR -
취득원가 (취득): 자산을 매수할 때
{}를 사용하여 원가를 설정합니다.Assets:Invest 10 STOCK {100.00 USD} -
둘 다 (가격 기록이 있는 매도): 자산을 매도할 때
{}를 사용하여 매도되는 로트를 식별하고@를 사용하여 매도 가격을 기록합니다. 이를 통해 자동화된 자본 이득 계산이 가능합니다.Assets:Invest -10 STOCK {100.00 USD} @ 105.00 USD이 기입은 각각 $100.00의 원가인 로트에서
STOCK10주를 각각 $105.00의 매도 가격으로 매도합니다.
독립형 price 지시문은 시장 평가를 위한 참조 데이터를 제공합니다. 실시간 가격은 호스팅 원장에서 지원되는 자산에 대해 이러한 지시문을 유지 관리할 수 있습니다. 새로 고침은 로트, 부킹 방법, 취득원가, 기록된 매도 대금을 변경하지 않습니다.
가격 사용 규칙
- 가격 주석(
@)은 어느 로트가 부킹되는지에 영향을 주지 않습니다. 로트 일치는 취득원가({})와 계정의 부킹 방법에 의해서만 처리됩니다. @기호는 다음에만 사용됩니다:
- 통화 변환.
- 거래 시점의 자산 시장 가치 기록.
- 자본 이득 계산을 위한 매도 가격 제공.
구성
부킹 방법은 전역적으로 또는 계정별로 구성할 수 있습니다.
전역 예약 방식
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"모범 사례
-
재고 구성: 원장을 깨끗하고 단순하게 유지하려면, 보유한 각 고유 상품에 대해 별도의 계정을 사용하고
open지시문에서 각 계정을 해당 상품으로 제한하는 것이 적극 권장됩니다.; 좋음: 상품별로 별도 계정, 각각 하나로 제한됨 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 ; 이득: 10%
- 디버깅: 오류나 예상치 못한 동작이 발생하면 Beancount는 재고 상태를 검사할 수 있는 도구를 제공합니다.
-
재고 상태 검사:
bea doctor context main.beancount 42를 사용하여 42번 줄의 거래를 검사합니다, 기입과 영향을 받은 계정 잔액을 포함하여. 파일명과 줄 번호를 검사하려는 거래로 교체하세요.거래 효과를 확인하려면
<LINENO>를 거래 직후의 줄 번호로 교체하세요. -
로트 일치 검증:
bea check도구는 전체 파일을 검증합니다.STRICT모드의 모호한 로트 일치와 같은 모든 부킹 오류를 잡아냅니다.