Beancount(평문 복식부기 회계 도구)와 Fava(그 웹 인터페이스)는 확장성과 스크립트 작성이 뛰어납니다. 이들의 설계 덕분에 Python 스크립트를 작성하여 재무 작업을 자동화하고, 사용자 정의 보고서를 생성하며, 알림을 설정할 수 있습니다. 한 사용자의 말을 빌리자면, "데이터를 이렇게 편리한 형식으로 가지고 있다는 점이 정말 마음에 들고, 원하는 대로 자동화할 수 있다는 점도 좋아합니다. 디스크에 있는 파일만큼 좋은 API는 없죠. 통합하기 쉽습니다." 이 가이드에서는 초보자 친화적인 자동화부터 고급 Fava 플러그인에 이르기까지 스크립트 워크플로우를 만드는 방법을 살펴봅니다.
실제 예시 원장을 살펴보세요:
bea 명령줄로 시작하기
Python을 작성하기 전에 bea로 이미 작업이 해결되는지 확인하세요. 이 명령은 원장을 검증하고, BQL 쿼리를 실행하며, 네 가지 재무 보고서를 생성하고 은행 내보내기 파일을 가져오며, 전역 --json 옵션으로 각 결과를 셸에서 jq로 파이프할 수 있는 파싱 가능한 형태로 변환합니다. 종료 코드는 예약된 작업이 분기하는 계약이므로 cron이나 CI에는 로더 스크립트가 전혀 필요하지 않습니다. 대상 해석, 결과 형식, 종료 코드 분기에 대해서는 bea로 부기 자동화하기를 참고하고, CLI가 제공하지 않는 사용자 정의 계산이 필요할 때 여기로 돌아오세요.
시작하기: Python 스크립트로 Beancount 실행하기
아래의 사용자 정의 Python 스크립트에는 스크립팅 라이브러리를 설치하세요(pip install beancount beanquery beangulp). bea 명령 워크플로우는 대신 관리형 엔진을 사용합니다. 설치하려면 CLI 빠른 시작을 따르세요. Beancount는 Python으로 작성되었기 때문에 자신의 스크립트에서 라이브러리로 사용할 수 있습니다. 아래 스크립트는 Beancount 3.2.3, beanquery 0.2.0, beangulp 0.2.0으로 실행되었습니다. 일반적인 접근 방식은 다음과 같습니다:
-
Beancount 원장 불러오기: Beancount의 로더를 사용해
.beancount파일을 Python 객체로 파싱합니다. 예를 들어:from beancount import loader entries, errors, options = loader.load_file("myledger.beancount") if errors: for error in errors: print(error) raise SystemExit(1)로더는 항목(entries)과 오류(errors)를 함께 반환합니다. 불균형하거나 유효하지 않은 파일도 항목을 반환하므로
errors를 확인하고 데이터를 신뢰하기 전에 중단하세요. 이제 모든 계정, 거래, 잔액에 코드로 접근할 수 있습니다. -
Beancount Query Language (BQL) 활용: 수동으로 반복하는 대신 SQL과 유사한 쿼리를 데이터에 실행할 수 있습니다. 쿼리는 별도의
beanquery패키지에 있습니다. Beancount 3.2.3에는beancount.query모듈이 없습니다. 예를 들어 월별 총 지출을 얻으려면 로드된 항목을 연결하고 쿼리를 직접 실행하세요:import beanquery conn = beanquery.connect("beancount:", entries=entries, errors=errors, options=options) cur = conn.execute( "SELECT year, month, sum(position) WHERE account ~ 'Expenses' GROUP BY year, month" ) for row in cur.fetchall(): print(row)이것은 beanquery를 사용해 데이터를 집계합니다.
bea query뒤에 있는 것과 동일한 엔진이지만, 여기서는 스크립트에서 직접 호출합니다. 이렇게 하면 루프에서 외부 명령을 셸로 호출하는 것을 피할 수 있습니다. -
프로젝트 구조 설정: 원장과 함께 스크립트를 구성하세요. 일반적인 레이아웃은 importers(외부 데이터 가져오기/파싱), reports 또는 queries(분석 스크립트), documents(다운로드한 명세서 저장) 디렉토리를 두는 것입니다. 예를 들어 한 사용자는 다음과 같이 유지합니다:
importers/– 사용자 정의 Python 가져오기 스크립트(테스트 포함),queries/– 보고서 생성 스크립트(python3 queries/...로 실행 가능),documents/– 계정별로 정리된 다운로드한 은행 CSV/PDF.
이 설정으로 스크립트를 수동으로 실행하거나(예: python3 queries/cash_flow.py) cron이나 작업 실행기를 통해 예약하여 워크플로우를 자동화할 수 있습니다.
조정 작업 자동화
**대사(reconciliation)**란 원장이 외부 기록(은행 명세서, 신용카드 보고서 등)과 일치하는지 확인하는 것을 의미합니다. Beancount의 평문 원장과 Python API 덕분에 이 과정의 상당 부분을 자동화할 수 있습니다.
거래 내역 가져오기 및 매칭 (초급)
초보자에게 권장되는 접근 방식은 별도의 beangulp 패키지의 importer를 사용하는 것입니다. Beancount 3에서는 v2 수집 모듈과 그 extract 명령이 제거되었습니다. beangulp.Importer를 상속받는 작은 Python 클래스를 작성하여 주어진 형식(CSV, OFX, PDF 등)을 파싱하고 거래를 생성합니다. 짧은 수집 스크립트에 등록한 다음 관리형 엔진에서 bea ingest로 실행하세요:
- 은행의 CSV 형식에 맞는 importer(
identify(),account(),extract()메서드가 있는 Python 클래스)를 작성합니다. - importer를 등록하는 수집 스크립트를 추가합니다.
bea ingest는 스크립트의identify,extract,archive명령을 실행합니다. 예를 들어 한 워크플로우는~/Downloads의 모든 파일에 대해extract를 실행하고 거래를 임시 파일로 출력합니다. - 임시 파일의 거래를 수동으로 검토하고 메인 원장으로 복사한 다음,
bea check를 실행하여 잔액이 일치하는지 확인합니다.
최소한의 예시: date,description,amount 열이 있는 statement.csv를 다음 importer(checking_importer.py)로 파싱합니다:
import csv
import datetime
from beancount.core import data
from beancount.core.amount import Amount
from beancount.core.number import D
import beangulp
class CheckingImporter(beangulp.Importer):
def identify(self, filepath: str) -> bool:
return filepath.endswith("statement.csv")
def account(self, filepath: str) -> str:
return "Assets:Bank:Checking"
def extract(self, filepath: str, existing):
entries = []
with open(filepath, newline="") as f:
for row in csv.DictReader(f):
date = datetime.date.fromisoformat(row["date"])
amount = Amount(D(row["amount"]), "USD")
meta = data.new_metadata(filepath, 0)
entries.append(
data.Transaction(
meta, date, "*", None, row["description"],
data.EMPTY_SET, data.EMPTY_SET, [
data.Posting("Expenses:Food:Groceries", amount,
None, None, None, None),
data.Posting("Assets:Bank:Checking",
Amount(-amount.number, "USD"),
None, None, None, None),
]))
return entries수집 스크립트(ingest.py)가 이를 연결합니다:
from checking_importer import CheckingImporter
from beangulp import Ingest
ingest = Ingest([CheckingImporter()])
if __name__ == "__main__":
ingest()다운로드한 파일에 대해 실행하세요. 로컬 CSV에는 자격 증명이 필요하지 않습니다. 먼저 시스템 libmagic 라이브러리를 설치하세요. 일회성 활성화 명령은 Beangulp를 관리형 엔진으로 다운로드합니다:
bea engine enable beangulp
bea ingest identify --config ingest.py statement.csv
bea ingest extract --config ingest.py statement.csv -o new.beancountidentify는 파일에 대해 checking_importer.CheckingImporter를 보고합니다. extract는 거래를 Beancount 형식으로 작성합니다:
2024-01-08 * "Grocery Store"
Expenses:Food:Groceries 120.00 USD
Assets:Bank:Checking -120.00 USDnew.beancount를 검토하고, 항목을 메인 원장으로 복사한 다음 bea check를 실행하세요.
단일 명세서를 변환하는 데 importer를 작성할 필요가 없습니다. 파일을 CSV to Beancount 변환기에 붙여넣거나, .ofx, .qfx 및 .qif 다운로드에는 OFX & QIF to Beancount를 사용하세요. 둘 다 브라우저에서 완전히 실행되므로 명세서가 컴퓨터를 떠나지 않습니다.
이 과정에는 여전히 검토 단계가 포함되지만, 항목을 파싱하고 형식을 지정하는 번거로운 작업의 상당 부분이 자동화됩니다. Importer 스크립트는 범주를 자동 할당하고 불일치를 잡아내기 위해 잔액 assertion(예상 잔액 진술)을 설정할 수도 있습니다. 예를 들어 가져온 후에는 2025-04-30 balance Assets:Bank:Checking 1234.56 USD 같은 줄이 있어 마감 잔액을 주장할 수 있습니다. bea check를 실행하면 Beancount가 _이러한 모든 잔액 assertion이 올바른지 확인_하고, 거래가 누락되었거나 중복된 경우 오류를 표시합니다. 이것은 모범 사례입니다. 각 명세서 기간에 대해 잔액 assertion을 자동 생성하여 컴퓨터가 미대사 차이를 찾아내게 하세요.
맞춤 조정 스크립트 (중급)
더 많은 제어가 필요하면 사용자 정의 Python 스크립트를 작성하여 은행의 거래 목록(CSV 또는 API를 통해)을 원장 항목과 비교할 수 있습니다:
- 외부 데이터 읽기: Python의
csv모듈(또는 Pandas)로 은행의 CSV 파일을 파싱합니다. 데이터를 거래 목록으로 정규화합니다(예: 각각 날짜, 금액, 설명 포함). - 원장 거래 불러오기: 앞서 보여준 것처럼
loader.load_file을 사용해 모든 원장 항목을 가져옵니다. 이 목록을 관심 계정(예: 당좌 계좌)과 명세서의 날짜 범위로 필터링하세요. - 비교하고 불일치 찾기:
- 각 외부 거래에 대해 원장에 동일한 항목이 있는지 확인합니다(날짜와 금액, 어쩌면 설명으로 매칭). 없으면 "새 항목"으로 표시하고 검토할 수 있도록 Beancount 형식의 거래로 출력할 수 있습니다.
- 반대로 외부 소스에 나타나지 않는 해당 계정의 원장 항목을 식별합니다. 이는 데이터 입력 오류일 수도 있고 아직 은행에서 처리되지 않은 거래일 수도 있습니다.
- 결과 출력: 보고서를 출력하거나 누락된 거래가 포함된 새
.beancount스니펫을 만듭니다.
예를 들어, **reconcile.py**라는 커뮤니티 스크립트가 정확히 이 작업을 수행합니다. Beancount 파일과 입력 CSV가 주어지면 가져와야 할 새 거래 목록과 입력에 없는 기존 원장 전기(잘못 분류되었을 가능성의 신호)를 출력합니다. 이런 스크립트를 사용하면 월별 대사가 실행하고 제안된 거래를 원장에 추가하는 것만큼 간단할 수 있습니다. 한 Beancount 사용자는 _"매월 모든 계정에 대해 대사 과정을 수행"_하며 성장하는 Python 코드 모음을 사용해 데이터 가져오기와 대사의 많은 수동 작업을 제거한다고 합니다.
팁: 대사 중 정확성을 위해 Beancount의 도구를 활용하세요:
- 언급한 대로 잔액 assertion을 사용해 계정 잔액을 자동 확인하세요.
- 원하면
pad지시어를 사용해 소소한 반올림 차이에 대한 균형 항목을 자동 삽입할 수 있습니다(주의해서 사용). - importer 또는 대사 로직에 단위 테스트를 작성하세요(Beancount는 테스트 도우미를 제공합니다). 예를 들어 한 워크플로우는 샘플 CSV를 가져와 예상 거래로 실패하는 테스트를 작성한 다음 모든 테스트가 통과할 때까지 importer를 구현했습니다. 이렇게 하면 import 스크립트가 다양한 경우에 대해 올바르게 작동합니다.
맞춤 보고서 및 요약 생성
Fava는 많은 표준 보고서(손익계산서, 대차대조표 등)를 제공하지만, 스크립트로 사용자 정의 보고서를 만들 수 있습니다. 이는 단순한 콘솔 출력부터 서식이 풍부한 파일이나 차트까지 다양합니다.
보고서용 데이터 조회 (초급)
기본 수준에서 Beancount Query Language(BQL)를 사용해 요약 데이터를 가져오고 출력하거나 저장할 수 있습니다. 예를 들어:
-
현금 흐름 요약: 쿼리를 사용해 순현금 흐름을 계산합니다. "현금 흐름"은 일정 기간 동안 특정 계정의 잔액 변화로 정의될 수 있습니다. BQL을 사용하면 다음과 같이 할 수 있습니다:
SELECT year, month, sum(position) WHERE account ~ 'Income' OR account ~ 'Expenses' GROUP BY year, month이것은 월별로 모든 수입과 지출 전기를 순액화합니다.
~와 정규식으로 필터링하세요.LIKE는 beanquery 0.2.0에서 문법 오류입니다. 전기는amount가 아니라position을 가집니다. 각 행은 하나의 Inventory를 가지므로 모든 통화가 변환되지 않고 개별적으로 나열됩니다. 수입은 음수, 지출은 양수로 도착합니다. 이 쿼리를bea query로 또는 앞서 보여준 beanquery Python API로 실행한 다음 결과를 포맷할 수 있습니다. -
범주 지출 보고서: 범주별 총 지출을 쿼리합니다:
SELECT account, sum(position) WHERE account ~ 'Expenses' GROUP BY account ORDER BY sum(position) ASC이것은 범주별 지출 테이블을 생성합니다. 각 합계는 원래 통화의 Inventory입니다. 집계를
round()로 감싸지 마세요.round(inventory, int)함수가 없으므로round(sum(position), 2)는 컴파일되지 않습니다. 스크립트에서 여러 쿼리를 실행하고 결과를 텍스트, CSV, 또는 추가 처리를 위한 JSON으로 출력할 수 있습니다.
한 사용자는 Fava 또는 스크립트로 재무 데이터를 분석하는 것이 _"사소한 일"_이라고 느꼈으며, 하나의 Python 스크립트로 Query Language를 통해 Beancount에서 데이터를 추출한 다음 Pandas DataFrame에 넣어 사용자 정의 보고서를 준비한다고 언급했습니다. 예를 들어 쿼리로 월별 합계를 가져온 다음 Pandas/Matplotlib으로 시간 경과에 따른 현금 흐름 차트를 그릴 수 있습니다. BQL과 데이터 과학 라이브러리의 조합으로 Fava가 기본적으로 제공하는 것 이상의 보고서를 만들 수 있습니다.
고급 보고 (차트, 성능 등)
더 고급 요구 사항의 경우 스크립트로 투자 성과 같은 지표를 계산하거나 시각적 출력을 만들 수 있습니다:
-
투자 성과 (IRR/XIRR): 원장에 모든 현금 흐름(매수, 매도, 배당금)이 있으므로 포트폴리오 수익률을 계산할 수 있습니다. 예를 들어 투자 계정의 거래를 필터링한 다음 내부 수익률(Internal Rate of Return)을 계산하는 스크립트를 작성할 수 있습니다. 현금 흐름 데이터가 주어지면 IRR을 계산하는 라이브러리(또는 공식)가 있습니다. 일부 커뮤니티에서 개발한 Fava 확장(예: PortfolioSummary 또는 fava_investor)이 정확히 이 작업을 수행하여 투자 포트폴리오의 IRR과 기타 지표를 계산합니다. 스크립트로는 기여금/인출금 시계열과 최종 가치에 IRR 함수(NumPy 또는 자체)를 사용할 수 있습니다.
-
다중 기간 또는 사용자 정의 지표: 매월 저축률(저축 대비 수입 비율) 보고서를 원하시나요? Python 스크립트로 원장을 로드하고, 모든 수입 계정과 모든 지출 계정을 합산한 다음, 저축 = 수입 - 지출과 그 비율을 계산할 수 있습니다. 이는 멋진 테이블을 출력하거나 기록용 HTML/Markdown 보고서를 생성할 수도 있습니다.
-
시각화: Fava 외부에서 차트를 생성할 수 있습니다. 예를 들어 스크립트에서
matplotlib또는altair를 사용해 원장 데이터로 시간 경과에 따른 순자산 차트를 만듭니다. 원장에는 모든 과거 잔액이 있으므로(또는 항목을 반복하여 누적할 수 있음) 시계열 플롯을 만들 수 있습니다. 이 차트를 이미지나 대화형 HTML로 저장하세요. (앱 내 시각화를 선호한다면, Fava 내부에 차트를 추가하는 방법은 아래 Fava 확장 섹션을 참조하세요.)
출력 옵션: 보고서를 전달하는 방법을 결정하세요:
- 일회성 분석의 경우 화면에 출력하거나 CSV/Excel 파일로 저장하는 것으로 충분할 수 있습니다.
- 대시보드의 경우 브라우저에서 열 수 있는 HTML 파일로 데이터를 생성하는 것을 고려하세요(Jinja2 같은 템플릿 라이브러리를 사용하거나 Markdown을 작성해도 됩니다).
- Jupyter Notebook과 통합해 대화형 보고 환경을 만들 수도 있지만, 이는 자동화보다 탐색에 더 적합합니다.
원장에서 알림 트리거하기
스크립트 워크플로우의 또 다른 강력한 용도는 재무 데이터의 조건에 따라 알림을 설정하는 것입니다. 원장은 정기적으로 업데이트되고(예정된 청구서나 예산 같은 미래 날짜 항목을 포함할 수도 있음) 스크립트로 스캔하여 중요한 이벤트에 대한 알림을 받을 수 있습니다.
계좌 잔액 부족 경고
당좌 대월을 피하거나 최소 잔액을 유지하기 위해 계정(예: 당좌 또는 저축)이 임계값 아래로 떨어지면 알림을 받고 싶을 수 있습니다. 구현 방법은 다음과 같습니다:
-
현재 잔액 확인: 로더로
entries를 로드한 후 관심 계정의 최신 잔액을 계산합니다. 전기를 집계하거나 쿼리를 사용해 이 작업을 수행할 수 있습니다. 예를 들어 특정 계정의 잔액에 BQL 쿼리를 사용하세요:SELECT sum(position) WHERE account = 'Assets:Bank:Checking'이것은 해당 계정의 현재 잔액(모든 전기의 합)을 반환합니다. 또는 Beancount의 내부 함수를 사용해 대차대조표를 작성할 수 있습니다. 예를 들어:
from beancount.core import realization tree = realization.realize(entries) acct = realization.get_or_create(tree, "Assets:Bank:Checking") balance = acct.balance # an Inventory of commodities항목만 전달하세요. 두 번째 매개변수는 옵션 맵이 아니라
min_accounts입니다. 그런 다음 숫자 값을 추출합니다(예:balance.get_currency_units('USD')는 USD의 Decimal 금액을 반환). 쿼리 집계처럼 잔액은 모든 통화를 개별적으로 유지합니다. 그러나 대부분의 경우 쿼리를 사용하는 것이 더 간단합니다. -
임계값 확인: 잔액을 미리 정의한 한도와 비교합니다. 아래면 알림을 트리거합니다.
-
알림 트리거: 콘솔에 경고를 출력하는 것만큼 간단할 수 있지만, 실제 알림의 경우 이메일이나 푸시 알림을 보낼 수 있습니다. 이메일(
smtplib를 통해)이나 IFTTT 또는 Slack의 웹훅 API 같은 서비스와 통합해 알림을 푸시할 수 있습니다. 예를 들어:if balance < 1000: send_email("Low balance alert", f"Account XYZ balance is {balance}")(이메일 서버 세부 정보로
send_email을 구현하세요.)
이 스크립트를 매일 실행하면(cron 작업 또는 Windows 작업 스케줄러를 통해) 사전 예방적 경고를 받게 됩니다. 원장을 사용하기 때문에 방금 추가한 거래를 포함한 모든 거래를 고려할 수 있습니다.
다가오는 지불 마감일
Beancount를 사용해 청구서나 기한을 추적한다면 미래 지불을 표시하고 스크립트가 알려주도록 할 수 있습니다. Beancount에서 다가오는 의무를 표현하는 두 가지 방법:
-
이벤트: Beancount는 임의의 날짜 메모를 위한
event지시어를 지원합니다. 예를 들어:2025-05-10 event "BillDue" "Mortgage payment due"이것은 잔액에 영향을 주지 않지만 레이블이 있는 날짜를 기록합니다. 스크립트가
entries에서Event.type == "BillDue"(또는 선택한 사용자 정의 유형)인Event항목을 스캔하고 날짜가 오늘부터 예를 들어 향후 7일 이내인지 확인할 수 있습니다. 그렇다면 알림(이메일, 알림, 또는 팝업)을 트리거합니다. -
미래 거래: 일부 사람들은 예정된 지불 같은 것에 대해 미래 날짜 거래(미래 날짜로 기재)를 입력합니다. 이는 날짜가 지나기 전까지 잔액에 나타나지 않습니다(미래 날짜 기준으로 보고서를 실행하지 않는 한). 스크립트가 가까운 미래 날짜의 거래를 찾아 나열할 수 있습니다.
이를 사용해 실행 시 곧 만기가 되는 작업이나 청구서 목록을 출력하는 "티클러(tickler)" 스크립트를 만들 수 있습니다. Google Calendar나 작업 관리자 같은 API와 통합하면 자동으로 알림을 만들 수 있습니다.
이상 탐지
알려진 임계값이나 날짜를 넘어, 비정상적인 패턴에 대한 사용자 정의 알림을 스크립트로 만들 수 있습니다. 예를 들어 정상적으로 매월 발생하는 지출이 발생하지 않았거나(청구서 지불을 잊었을 수 있음), 또는 이번 달 범주의 지출이 비정상적으로 높으면 스크립트가 이를 표시할 수 있습니다. 이는 일반적으로 최근 데이터를 쿼리하고 기록과 비교하는 것을 포함합니다(고급 주제일 수 있으며 통계나 머신러닝을 사용할 수도 있습니다).
실제로 많은 사용자가 이상(예상치 못한 거래)을 잡아내기 위해 대사에 의존합니다. 은행 알림(각 거래에 대한 이메일 등)을 받는다면 스크립트로 이를 파싱하고 Beancount에 자동으로 추가하거나 적어도 기록되었는지 확인할 수 있습니다. 한 열성 사용자는 은행이 거래 알림 이메일을 보내도록 설정하고, 이를 자동으로 파싱해 원장에 추가할 계획을 세웠습니다. 이런 종류의 이벤트 기반 알림은 기록되지 않은 거래가 없도록 보장할 수 있습니다.
Fava를 맞춤 플러그인과 뷰로 확장하기
Fava는 이미 확장 시스템을 통해 스크립트 작성이 가능합니다. 자동화나 보고서를 웹 인터페이스에 직접 통합하고 싶다면 Python으로 Fava 확장(플러그인이라고도 함)을 작성할 수 있습니다.
Fava 확장의 작동 방식: 확장은 fava.ext.FavaExtensionBase를 상속받는 클래스를 정의하는 Python 모듈입니다. 사용자 정의 옵션을 통해 Beancount 파일에 등록합니다. 예를 들어 MyAlerts(FavaExtensionBase) 클래스가 있는 myextension.py 파일이 있다면 원장에 다음을 추가해 활성화할 수 있습니다:
1970-01-01 custom "fava-extension" "myextension"Fava가 로드되면 해당 모듈을 가져오고 MyAlerts 클래스를 초기화합니다.
확장은 여러 가지를 할 수 있습니다:
- 훅(Hooks): Fava 생명주기의 이벤트에 훅을 걸 수 있습니다. 예를 들어
after_load_file()은 원장이 로드된 후 호출됩니다. 이를 사용해 검사를 실행하거나 데이터를 미리 계산할 수 있습니다. Fava 내부에 낮은 잔액 검사를 구현하려면after_load_file이 계정 잔액을 반복하고 경고를 저장할 수 있습니다(다만 이를 UI에 표시하려면 FavaAPIError를 발생시키거나 Javascript로 알림을 표시하는 등 약간 더 작업이 필요할 수 있습니다). - 사용자 정의 보고서/페이지: 확장 클래스가
report_title속성을 설정하면 Fava가 사이드바에 새 페이지를 추가합니다. 그런 다음 해당 페이지 내용을 위한 템플릿(HTML/Jinja2)을 제공합니다. 이렇게 Fava가 기본적으로 가지지 않은 완전히 새로운 뷰(예: 대시보드나 요약)를 만듭니다. 확장은 필요한 모든 데이터를 수집할 수 있으며(self.ledger에 모든 항목, 잔액 등에 접근 가능) 템플릿을 렌더링합니다.
예를 들어, Fava의 내장 portfolio_list 확장은 포트폴리오 포지션을 나열하는 페이지를 추가합니다. 커뮤니티 확장은 더 나아갑니다:
- 대시보드: fava-dashboards 플러그인은 사용자 정의 차트와 패널(Apache ECharts 같은 라이브러리 사용)을 정의할 수 있게 합니다. 실행할 쿼리의 YAML 설정을 읽고 Beancount를 통해 실행한 다음, Fava에서 동적 대시보드 페이지를 생성합니다. 본질적으로 Beancount 데이터와 JavaScript 차트 라이브러리를 함께 묶어 대화형 시각화를 만듭니다.
- 포트폴리오 분석: PortfolioSummary 확장(사용자 기여)은 투자 요약(계정 그룹화, IRR 계산 등)을 계산해 Fava UI에 표시합니다.
- 거래 검토: 또 다른 확장인 fava-review는 시간 경과에 따른 거래 검토를 돕습니다(예: 영수증을 놓치지 않았는지 확인).
간단한 확장을 직접 만들려면 FavaExtensionBase를 서브클래싱하는 것으로 시작하세요. 예를 들어 페이지를 추가하는 최소한의 확장은 다음과 같습니다:
from fava.ext import FavaExtensionBase
class HelloReport(FavaExtensionBase):
report_title = "Hello World"
def __init__(self, ledger, config):
super().__init__(ledger, config)
# any initialization, perhaps parse config if provided
def after_load_file(self):
# (optional) run after ledger is loaded
print("Ledger loaded with", len(self.ledger.entries), "entries")이것을 hello.py에 넣고 원장에 custom "fava-extension" "hello"를 추가하면 Fava는 새 "Hello World" 페이지를 표시합니다(확장이 훅만 사용하지 않는 한 페이지 내용을 정의하려면 templates 하위 폴더에 HelloReport.html 템플릿 파일도 필요합니다). 템플릿은 확장 클래스에 첨부한 데이터를 사용할 수 있습니다. Fava는 Jinja2 템플릿을 사용하므로 해당 템플릿에서 데이터를 HTML 테이블이나 차트로 렌더링할 수 있습니다.
참고: Fava의 확장 시스템은 강력하지만 "불안정"(변경될 수 있음)으로 간주됩니다. 사용자 정의 페이지를 만들려면 웹 개발(HTML/JS)에 대한 어느 정도의 친숙함이 필요합니다. 단순히 스크립트나 분석을 실행하는 것이 목표라면 외부 스크립트로 유지하는 것이 더 쉬울 수 있습니다. 워크플로우를 위한 맞춤형 인앱 경험을 원할 때 Fava 확장을 사용하세요.
서드파티 API 및 데이터 통합
스크립트 워크플로우의 장점 중 하나는 외부 데이터를 가져올 수 있다는 것입니다. 일반적인 통합은 다음과 같습니다:
호스팅 평가 가격에는 Live Prices가 예약된 가격 가져오기 스크립트 없이 관리형 include를 제공합니다. 선택기에서 지원되는 자산 쌍과 견적 통화를 선택하세요. 아래의 로컬 파일 기반 워크플로우는 업스트림 Beancount, Fava, 재현 가능한 보고서에 여전히 유용합니다. 관리형 새로 고침은 원장에 Git 커밋을 만들지 않습니다.
-
환율 및 상품: 업스트림 Beancount는 가격을 스스로 가져오지 않지만, 환율을 제공하기 위한
price지시어를 제공합니다. 이 가격 가져오기를 자동화할 수 있습니다. 예를 들어 스크립트가 API(Yahoo Finance, Alpha Vantage 등)에 최신 환율이나 주가를 쿼리하고 원장에 price 항목을 추가할 수 있습니다:2025-04-30 price BTC 30000 USD 2025-04-30 price EUR 1.10 USD관리형 엔진의 Beanprice를 백엔드로 하는
bea price같은 도구가 있어 일일 시세를 가져와 Beancount 형식으로 출력합니다.bea engine enable beanprice로 한 번 활성화한 다음,bea price main.beancount를 매일 밤 실행하여prices.beancountinclude 파일을 업데이트하도록 예약할 수 있습니다. 또는 Python을 사용하세요. 예를 들어requests라이브러리로 API를 호출합니다. Beancount 문서에 따르면 공개 거래 자산의 경우 "가격을 다운로드하고 지시어를 작성해 주는 코드를 호출"할 수 있습니다. 즉, 수동으로 하는 대신 스크립트가 조회하고price줄을 삽입하도록 하세요. -
주식 포트폴리오 데이터: 환율과 유사하게 API와 통합해 상세한 주식 데이터나 배당금을 가져올 수 있습니다. 예를 들어 Yahoo Finance API(또는
yfinance같은 커뮤니티 라이브러리)는 티커의 과거 데이터를 검색할 수 있습니다. 스크립트가 보유한 각 주식의 월별 가격 기록으로 원장을 업데이트하여 정확한 시장 가치 역사 보고서를 가능하게 할 수 있습니다. 일부 사용자 정의 확장(예: fava_investor)은 표시를 위해 즉석에서 가격 데이터를 가져오기도 하지만, 가장 간단한 방법은 정기적으로 가격을 원장에 가져오는 것입니다. -
뱅킹 API (오픈 뱅킹/Plaid): CSV를 다운로드하는 대신 API를 사용해 거래를 자동으로 가져올 수 있습니다. Plaid 같은 서비스는 은행 계정을 집계하고 거래에 대한 프로그래밍 방식의 접근을 허용합니다. 고급 설정에서는 Python 스크립트가 Plaid의 API를 사용해 매일 새 거래를 가져와 파일에 저장하거나(원장에 직접 가져오거나) 할 수 있습니다. 한 파워 유저는 Plaid가 자신의 가져오기 파이프라인에 공급되어 장부가 거의 자동이 되는 시스템을 구축했습니다. 그들은 "Plaid API에 가입해서 로컬에서 같은 것을 하는 것을 막을 것은 없다"고 언급했습니다. 즉, 로컬 스크립트를 작성해 은행 데이터를 가져온 다음 Beancount importer 로직으로 파싱해 원장 항목으로 만들 수 있습니다. 일부 지역에서는 은행이 제공하는 오픈 뱅킹 API가 있으며, 유사하게 사용할 수 있습니다.
-
기타 API: 예산 도구(Beancount의 실제와 비교할 계획된 예산 내보내기)를 통합하거나 OCR API를 사용해 영수증을 읽고 거래와 자동 매칭할 수 있습니다. 스크립트가 Python 생태계에 완전히 접근할 수 있으므로 이메일 서비스(알림 전송)부터 Google Sheets(예: 월별 재무 지표로 시트 업데이트), 메시징 앱(Telegram 봇으로 요약 보고서 전송)까지 모든 것을 통합할 수 있습니다.
서드파티 API를 사용할 때는 자격 증명을 보호하고(API 키에 환경 변수나 구성 파일 사용), 스크립트에서 오류(네트워크 문제, API 다운타임)를 우아하게 처리하는 것을 잊지 마세요. 데이터를 캐시하는 것도 종종 현명합니다(예: 가져온 환율을 저장해 동일한 과거 환율을 반복 요청하지 않도록).
모듈화되고 유지보수하기 쉬운 스크립트를 위한 모범 사례
스크립트 워크플로우를 구축할 때 코드를 체계적이고 견고하게 유지하세요:
-
모듈성: 서로 다른 관심사를 다른 스크립트나 모듈로 분할하세요. 예를 들어 "데이터 가져오기/대사" vs. "보고서 생성" vs. "알림"에 대해 별도의 스크립트를 두세요.
ledger_import.py,ledger_reports.py같은 모듈이 있는 작은 Python 패키지를 원장용으로 만들 수도 있습니다. 이렇게 하면 각 부분을 이해하고 테스트하기 쉬워집니다. -
구성: 값을 하드코딩하지 마세요. 계정 이름, 임계값, API 키, 날짜 범위 등에 구성 파일이나 스크립트 상단의 변수를 사용하세요. 이렇게 하면 코드를 깊이 수정하지 않고도 쉽게 조정할 수 있습니다. 예를 들어 상단에
LOW_BALANCE_THRESHOLDS = {"Assets:Bank:Checking": 500, "Assets:Savings": 1000}를 정의하면 알림 스크립트가 이 딕셔너리를 반복할 수 있습니다. -
테스트: 재무 자동화를 미션 크리티컬 코드로 취급하세요. 실제로 그렇습니다! 복잡한 로직에 테스트를 작성하세요. Beancount는 importer 테스트에 내부적으로 사용되는 테스트 도우미를 제공하며, 이를 활용해 원장 입력을 시뮬레이션할 수 있습니다. 화려한 프레임워크 없이도 더미 CSV와 예상 출력 거래가 있으면 import 스크립트가 올바른 항목을 생성하는지 확인할 수 있습니다.
pytest를 사용하면 이러한 테스트를 쉽게 통합할 수 있습니다(Alex Watt가 pytest를 감싸는just test명령으로 한 것처럼). -
버전 관리: 원장과 스크립트를 버전 관리(git) 아래에 두세요. 이는 백업과 기록을 제공할 뿐만 아니라 통제된 방식으로 변경하도록 장려합니다. "재무 스크립트" 릴리스에 태그를 달거나 문제를 디버깅할 때 차이를 검토할 수 있습니다. 일부 사용자는 시간 경과에 따른 변경을 보기 위해 재무 기록을 Git으로 추적하기도 합니다. 단, 저장소에서 민감한 데이터(원본 명세서 파일이나 API 키 등)를 무시하도록 주의하세요.
-
문서화: 미래의 자신을 위해 사용자 정의 워크플로우를 문서화하세요. 환경 설정 방법, 각 스크립트 실행 방법, 각 스크립트의 기능을 설명하는 저장소의 README는 몇 달이 지난 후 매우 귀중합니다. 특히 명확하지 않은 회계 로직이나 API 상호 작용에 대해서는 코드에 주석을 다세요.
-
Fava 플러그인 유지 관리: Fava 확장을 작성한다면 단순하게 유지하세요. Fava는 변경될 수 있으므로 기능이 집중된 작은 확장이 업데이트하기 쉽습니다. 너무 많은 로직을 중복하지 마세요. 원장 변경에 민감할 수 있는 계산을 하드코딩하는 대신 가능하면 Beancount의 쿼리 엔진이나 기존 도우미 함수를 사용하세요.
-
보안: 스크립트가 민감한 데이터를 처리하고 외부 서비스에 연결할 수 있으므로 주의해서 다루세요. API 키를 노출하지 말고 안전한 컴퓨터에서 자동화를 실행하는 것을 고려하세요. 호스팅 솔루션이나 클라우드(GitHub Actions를 예약하거나 Fava를 실행하는 서버 등)를 사용하는 경우, 원장 데이터가 저장 시 암호화되어 있고 개인 정보 보호 영향에 대해 편안하게 느끼는지 확인하세요.
이러한 관행을 따르면 재무(와 도구 자체)가 발전하더라도 워크플로우가 안정적으로 유지됩니다. 최소한의 수정으로 해마다 재사용할 수 있는 스크립트를 원할 것입니다.
결론
Beancount와 Fava는 기술에 능숙한 사용자가 개인 재무 추적을 완전히 맞춤화할 수 있는 강력하고 유연한 플랫폼을 제공합니다. Python 스크립트를 작성하면 명세서 대사 같은 지루한 작업을 자동화하고, 필요에 맞춘 풍부한 보고서를 생성하며, 적시 알림으로 재무를 파악할 수 있습니다. 우리는 기본부터 고급까지 다양한 예시를 다루었습니다. 단순한 쿼리와 CSV 가져오기부터 시작해 완전한 Fava 플러그인과 외부 API 통합으로 나아갔습니다. 이를 구현할 때는 단순하게 시작하고 점진적으로 구축하세요. 몇 개의 작은 자동화 스크립트만으로도 몇 시간의 작업을 절약하고 정확성을 크게 향상시킬 수 있습니다. 그리고 모든 것이 평문과 Python이라는 사실을 기억하세요. 완전히 통제할 수 있습니다. 재무 시스템이 여러분과 함께 성장합니다, 특정 요구에 맞춰 휘어지면서. 즐거운 스크립팅 되세요!
출처: 위의 기법은 Beancount 문서와 커뮤니티 경험에서 가져왔습니다. 더 읽어보려면 Beancount 공식 문서, 커뮤니티 가이드와 블로그, 유용한 플러그인과 도구 링크를 위한 Awesome Beancount 저장소를 참조하세요.